# Saleae MSO API > Control Saleae Logic MSO mixed-signal oscilloscope devices. For Logic MSO hardware only (not Logic Analyzers). ## Guides ## Getting Started This guide will help you get started with the Saleae MSO API. ### Prerequisites Before starting, please follow the [Installation](installation) guide to set up your environment with the necessary requirements and dependencies. We recommend opening up Logic2 and testing that your Logic MSO can perform basic functions on your machine before diving into automation. ### Basic Usage Here's a simple example of how to capture an analog signal: ```python from pathlib import Path import numpy as np from saleae import mso_api mso = mso_api.MSO() capture_config = mso_api.CaptureConfig( enabled_channels=[mso_api.AnalogChannel(channel=0, name="clock")], analog_settings=mso_api.AnalogSettings(sample_rate=100e6), capture_settings=mso_api.TimedCapture(capture_length_seconds=0.1), ) for n in range(3): save_dir = Path('my-captures') / f'{n:02d}' capture = mso.capture(capture_config, save_dir=save_dir) avg_voltage = np.mean(capture.analog_data["clock"].voltages) print(f"Capture {n:02d} in {save_dir} avg voltage: {avg_voltage:.3f} V") ``` As you can see there's 4 basic things happening in this example: - Initializing the `MSO` object - Configuring the capture by building a `CaptureConfig` object - Triggering one or more captures with that configuration - Processing the data from those captures (however you want) Let's dive into each one a little bit below. ### Initializing the `MSO` object The `MSO` object is your main interface to the Logic MSO hardware. You can initialize it with no arguments to connect to the first available device, or provide a serial number to connect to a specific device. ### Configuring a capture The `CaptureConfig` object controls all aspects of how a capture will be performed - which channels to capture from, sample rates, trigger conditions, and capture duration. You can configure analog channels, digital channels, or both for mixed-signal captures. Not all sample rates will be supported by all devices, so the API will make the capture at the closest supported sampling rate to the one in the configuration. Each capture object is returned with a configuration object which accurately represents the configuration the device used to make the capture, including the exact sample rate. ### Triggering a capture with that configuration Once you have an `MSO` object and a `CaptureConfig`, you can trigger a capture using the `Mso.capture` method. Capture files are saved to disk directly by the underlying MSO communications binary, and can be absolutely enormous depending on their length and sample rate. Top line Logic MSO devices can take >6GB single captures (2 bytes per sample with a tiny header in each file). The method returns a `Capture` object that provides access to the captured data. `Capture` objects use `numpy.memmap()` as their underlying storage mechanism so huge captures don't necessarily have to be stored in memory for certain operations to work on them. `Capture` objects are also persisted on disk by default, and are really easy to re-create quickly from their folder locations. ### Processing the data Here's where the fun begins! If you're using huge capture files, be mindful of what operations might cause them to be loaded into memory vs. operated on in-place. If you are only storing computation results from your captures, use `capture.delete()` to delete the underlying data files, or just `rm` the entire directory after calling `del` on your capture. If you have an interesting use case, we'd love to hear about it - send us a message on the support site! ### Next Steps - See the [Installation](installation) page for detailed setup instructions - Check out the [Usage](usage) guide for more detailed examples ## Installation This guide will help you install the Saleae MSO API. ### Requirements - Python 3.11 or higher - Logic 2.4.20 or newer installed (for the Logic MSO drivers, or udev rules) - A supported operating system, either: - Windows 10, or 11 (x64), or - Ubuntu 20.04+ (64-bit) - Mac OSX 10.14 (Mohave) or newer - Enough disk space for your capture files The remaining package requirement will be installed automatically by `uv` (or `pip`). We recommend installing a recent version of Logic2 on your system and checking that it can connect with and get data from your MSO before installing this library. We hope there will be no issues installing this library and getting your Saleae products operating automatically, but if there are issues then that step will make debugging them a much smoother and more targeted process. ### Installation Methods #### Saleae Python Package Index This package is distributed via a private python package index hosted by Saleae. You can install it with `uv` (recommended, see the [uv installation instructions](https://docs.astral.sh/uv/getting-started/installation/) for information), `pip`, `poetry` or any other python package manager. Saleae recommends using both `uv` and a virtual environment. You will need to provide the URL for our index when you install: ```bash uv add saleae-mso-api --index https://downloads.saleae.com/pypi --index-strategy unsafe-best-match ## or pip install saleae-mso-api --extra-index-url https://downloads.saleae.com/pypi/ ``` #### Linux udev Rules If you have not installed Logic2 before on your Linux system, you will need to install the `udev` rules into the proper place. After installing the package with the above instructions, run: ```bash ## For standard use (will prompt for password when it calls 'sudo') uv run python -m saleae.mso_api.utils.install_udev ## On pip with virtualenv source .venv/bin/activate python -m saleae.mso_api.utils.install_udev ``` This will put the rules into `/etc/udev/rules.d` and reload them with `udevadm`. You will see a new rules file called `99-SaleaeLogic.rules` that contains the following: ``` SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0925", ATTR{idProduct}=="3881", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1001", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1003", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1004", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1005", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1006", MODE="0666" SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="21a9", ATTR{idProduct}=="1007", MODE="0666" ``` ### Troubleshooting #### Common Issues - **ImportError: No module named 'saleae'**: Make sure you've installed the package correctly. - **ImportError: cannot import name 'X' from 'saleae'**: You might be using an older version of the package. Try updating. #### Getting Help If you encounter any issues during installation, please contact us at . ## Usage Guide This guide provides examples of how to use the Saleae MSO API for common tasks. For basic setup and your first capture, see the [Getting Started](getting_started) guide. ### Initiating a mixed-signal capture ```python from pathlib import Path from saleae import mso_api mso = mso_api.MSO() capture_config = mso_api.CaptureConfig( enabled_channels=[ mso_api.AnalogChannel(channel=0, name="clock"), mso_api.DigitalChannel(port=0, channel=0, name="MOSI", threshold_volts=1.6), ], analog_settings=mso_api.AnalogSettings(sample_rate=100e6), capture_settings=mso_api.TimedCapture(capture_length_seconds=0.1), ) save_dir = Path('my-capture') capture = mso.capture(capture_config, save_dir) ``` ### Loading a previously saved (or exported) capture The Logic2 software can export analog waveforms in a binary format using `File > Export Data` (not save!) To load a capture from a directory containing those binary export files, use the following: ```python from pathlib import Path from saleae import mso_api ## Specify the directory containing the capture files cap_dir = Path("../tests/data/capture1") ## Load the capture cap = mso_api.Capture.from_dir(cap_dir) ## Print information about the capture print(f"Channels: {cap.analog_channel_names}") print(f"Sample rate: {cap.analog_sample_rate} Hz") print(f"Duration: {cap.analog_stop_time - cap.analog_start_time:.6f} seconds") ``` ### Working with Triggers You can define triggers to identify specific events in your captures: ```python from saleae.mso_api import EdgeTrigger, EdgeTriggerDirection ## Create a trigger for rising edges on channel 1 (channel_index 0) at 0V trigger = EdgeTrigger( channel_index=0, threshold_volts=0, direction=EdgeTriggerDirection.RISING ) ## Create a trigger for falling edges on the "clock" channel at 1.65V with a 1ms holdoff trigger_with_holdoff = EdgeTrigger( channel_name="clock", threshold_volts=1.65, direction=EdgeTriggerDirection.FALLING, holdoff_seconds=0.001 ) ``` ### Applying Synthetic Triggers If you have a long capture that you need to split into multiples, use `split_capture()` ```python from pathlib import Path from saleae import mso_api from saleae.mso_api.synthetic_trigger import split_capture ## Load a capture cap_dir = Path("../tests/data/capture1") cap = mso_api.Capture.from_dir(cap_dir) ## Set up a new trigger to split on trigger = mso_api.EdgeTrigger( channel_index=0, threshold_volts=0, direction=mso_api.EdgeTriggerDirection.RISING ) ## Split the capture caps = split_capture(cap, trigger, pre_trigger_seconds=-6e-6, post_trigger_seconds=56e-6) print(f"Number of triggered segments: {len(caps)}") ## Output: Number of triggered segments: 13810 ``` ### Processing Triggered Segments Once you have split a capture into triggered segments, you can process each segment: ```python ## Process each triggered segment for i, segment in enumerate(caps[:5]): # Process just the first 5 segments as an example print(f"Segment {i}:") print(f" Duration: {segment.analog_stop_time - segment.analog_start_time:.9f} seconds") print(f" Samples: {segment.num_analog_samples}") # You can perform analysis on each segment for ch_name, ch_data in segment.analog_data.items(): print(f" {ch_name} min: {ch_data.voltages.min():.3f}V, max: {ch_data.voltages.max():.3f}V") ``` ## Release Notes ### v0.5.5 - February 25, 2026 - Update Logic MSO support to match Logic 2.4.42, which includes a new firmware version. ### v0.5.4 - December 12, 2025 - Fixed a crash when loading a binary capture with more than one trigger saved ### v0.5.3 - October 22, 2025 - Fixed executable permissions for macOS binaries (both x64 and arm64) - Binary files now automatically get execute permissions set on first use on macOS ### v0.5.2 - October 2025 - Added macOS support (x64 and arm64 architectures) ### v0.5.1 - September 16, 2025 - Added support for Rev0F analog front-end boards (missing from v0.5.0) ### v0.5.0 - July 2, 2025 - Added support for taking digital captures and mixed-signal captures - Added a debug plot capability ### v0.4.0 - May 23, 2025 - Added support for Linux ### v0.3.1 - May 13, 2025 - First public release - Support timed and triggered analog captures ## Contributing Thank you for your interest in contributing to the Saleae MSO API! This guide will help you get started with contributing to the project. ### Prerequisites Install the `uv` package manager. Their [installation instructions are here](https://docs.astral.sh/uv/getting-started/installation/), but the quick version may be to just do: ``` powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` ### Development Setup 1. Clone the repository: ```bash git clone git@github.com:saleae/mso-api.git cd mso-api ``` 2. Create a virtual environment using `uv` and activate it: ```bash uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate ``` 3. Install the package in development mode with the development (`dev`) dependencies into your virtualenv: ```bash uv sync --all-extras ``` 4. Install VISA drivers on your machine to run instrument-dependent tests These can be downloaded from [National Instruments here](https://www.ni.com/en/support/downloads/drivers/download.ni-visa.html). 5. Run the test suite - Plug in a Siglent function generator to your machine over USB - Plug the MSO into your machine - Supply power to the MSO's power USB-C port - Using a BNC to SMB cable (or a BNC cable with the MSO's included BNC-to-SMB adapter), plug both channels of the signal generator directly into the MSO's first two channels (CH1 to CH1, CH2 to CH2) - Run the test suite ``` uv run pytest tests ``` Or ``` .venv\Scripts\activate # VSCode wil typically do this automatically with the Python extension installed pytest tests ``` ### Code Style We use [ruff](https://github.com/charliermarsh/ruff) for code linting and formatting. To auto-format your code (do before each commit): ```bash ruff format ``` To lint: ```bash ruff check . ``` ### Testing We use pytest for testing. To run the tests: ```bash pytest tests ``` ### Documentation This project uses Jupyter Book for documentation. The documentation includes: - Getting started guide - Installation instructions - Usage examples - API reference - Contributing guidelines Building the docs cleanly currently requires an MSO attached to your system, as many of the code examples in the docs run live on hardware to get capture objects. They will build without an MSO, but the examples that do both run and depend on hardware will have tracebacks printed right after them. To build the documentation: 1. Install the development dependencies: ```bash uv sync --all-extras ## first time installation of the PDF generator requires this playwright install ``` 2. Build the HTML documentation: ```bash python build_docs.py --clean ``` This will generate HTML documentation in the `docs/_build/html` directory. 3. To also build a PDF version: ```bash python build_docs.py --pdf ``` 4. The documentation will be available at: - HTML: `docs/_build/html/index.html` - PDF: `docs/_build/pdf/book.pdf` (if built with `--pdf`) ### Pull Request Process 1. Fork the repository 2. Create a new branch for your feature or bugfix 3. Make your changes 4. Run the tests to ensure they pass 5. Update the documentation if necessary 6. Submit a pull request ### Reporting Issues If you find a bug or have a feature request, please open an issue on the GitHub repository. Please include: - A clear and descriptive title - A detailed description of the issue or feature request - Steps to reproduce the issue (if applicable) - Expected behavior - Actual behavior - Any relevant logs or screenshots ### Code of Conduct Please be respectful and considerate of others when contributing to this project. We strive to maintain a welcoming and inclusive environment for all contributors. ## Saleae MSO API Documentation Welcome to the documentation for the Saleae Mixed Signal Oscilloscope (MSO) API. ```{image} saleae_logo.jpg :alt: Saleae Logo :width: 150px :align: center ``` ### Overview This API provides programmatic access to Saleae Mixed Signal Oscilloscopes, allowing you to: - Configure and run analog, digital, or mixed-signal captures - Ingest the resulting binary data files into memory-mapped Numpy arrays, and - Some other things, like simple plotting of those captures The MSO API runs on Windows, Linux, and Mac computers, see for the exact versions of each supported. ### Installation See for detailed installation instructions. ### Quick Example The following demonstrates how to configure and execute a series of simple timed captures with the `saleae-mso-api` and a Logic MSO. ```{code-cell} python from pathlib import Path import numpy as np from saleae import mso_api mso = mso_api.MSO() capture_config = mso_api.CaptureConfig( enabled_channels=[mso_api.AnalogChannel(channel=0, name="clock")], analog_settings=mso_api.AnalogSettings(sample_rate=100e6), capture_settings=mso_api.TimedCapture(capture_length_seconds=0.1), ) for n in range(3): save_dir = Path('my-captures') / f'{n:02d}' capture = mso.capture(capture_config, save_dir=save_dir) avg_voltage = np.mean(capture.analog_data["clock"].voltages) print(f"Capture {n:02d} in {save_dir} avg voltage: {avg_voltage:.3f} V") ``` Learn more about how this example is structured by reading the following: - for configuring captures - for running them on an attached Logic MSO :::{warning} This is package is currently under heavy development, some significant features are missing and others may be evolving quickly. Things may change! ::: ### More * {ref}`genindex` * {ref}`modindex` * {ref}`search` ## Binary Files The `binary_files` module provides utilities for reading and parsing Verion 0 and Version 1 Saleae Logic2 binary export files. The Version 1 format was released in June 2025. See our [support website](https://support.saleae.com/faq/technical-faq/binary-and-csv-export-formats-2025-update) for information on the new format. ### Overview This module handles the low-level details of reading and parsing the binary file formats used by Saleae Logic devices. It's primarily used internally by the `Capture` class, but can also be used directly for advanced use cases. See the [Capture API Reference](capture.md) for more information. It defaults to using `np.memmap()` to access (what can be) very large capture files. This allows loading into system RAM to be done as late as practical. ### Functions #### `read_file` ```python def read_file(file_path: Path) -> SaleaeFile ``` Reads and parses a binary file exported from Saleae Logic. **Parameters:** - `file_path`: Path to the binary file **Returns:** - A `SaleaeFile` object containing the parsed contents ### Classes #### `SaleaeFile` Contains the parsed contents of a binary file. **Attributes:** - `version`: the file format version, either 0 or 1 - `type`: our internal Enum (e.g. Filetype_DigitalExport, _AnalogExport, _WaveformAdcExport, etc.) - `contents`: The parsed contents of the file (e.g., AnalogExport_V0, DigitalExport_V1, etc.) ### Note This module is primarily used internally by the `Capture` class. Most users should use the `Capture` class instead of working with this module directly. The module is exposed for advanced use cases where direct access to the binary file format is needed. ### Error Handling The module will raise appropriate errors if: - The file cannot be found - The file format is invalid - The file is not an analog export - The file is corrupted or incomplete ## Capture The `capture` module provides the core `Capture` class for loading, manipulating, and analyzing capture data from Saleae Logic devices. ### Overview The `Capture` class represents an individual capture from a Logic MSO device. It provides methods to: - Load capture data from binary files using `np.memmap` to avoid reading the whole capture into memory - Access channel data and metadata - Query information about the capture (channels, time range, etc.) ### Classes #### `Capture` ```python @dataclass class Capture: # the data in the capture, mappings from channel name to data analog_data: Dict[str, BaseAnalogData] digital_data: Dict[str, BaseDigitalData] # the config used to capture the data config: CaptureConfig ``` **Properties:** - `analog_start_time`: The start time of the capture in MSO-relative seconds - `analog_sample_rate`: The sample rate that analog traces were taken at in Hz - `analog_channel_names`: Returns a list of analog channel names in the capture - `analog_time_series`: Returns the time array for analog data - `analog_stop_time`: Returns the end time of the capture in seconds - `num_analog_samples`: Returns the number of analog samples in the capture **Class Methods:** - `from_dir`: Create a Capture from a directory containing .bin files ```python @classmethod def from_dir(cls, dir_path: Path) -> "Capture" ``` Call this method on previously saved capture directories to re-create the exact `Capture` object that existed when the capture was first returned, including the `CaptureConfig` object used to generate the capture in the first place. - `from_config`: Create a Capture from a CaptureConfig and a save path ```python @classmethod def from_config(cls, config: CaptureConfig, dir_path: Path) -> "Capture" ``` Call this when your previously saved capture doesn't have any .json files detailing the capture config in it. This may be the case if your capture came from a Logic2 export. **Methods:** - `delete`: Delete the capture files from disk Deletes all capture data files from disk. The capture must have been previously stored to disk (i.e., created via `from_dir` or `from_config`). Raises `RuntimeError` if the capture was not stored to disk. This method will: 1. Clear the analog and digital data arrays from memory 2. Run garbage collection to free memory, and 3. Delete all .bin files from the capture directory on disk ### Working with Capture Data The `analog_data` and `digital_data` attributes are dictionaries mapping channel names to `AnalogData` and `DigitalData` objects respectively. Channels must have unique names which will be generated for you if not supplied. #### Analog Data You can access continuous analog data using standard NumPy indexing. ```python ## Get voltage data for channel 1 (channel_index 0) ch1_volts = cap.analog_data["CH0_volts"].voltages ## Get a specific sample sample_index = 1000 voltage_at_sample = cap.analog_data["CH0_volts"].voltages[sample_index] ``` #### Digital Data Digital data is stored as an initial state and a list of transition times, which are relative to the trigger position (or the start of the capture). There are a couple of helper methods that we use internally to validate data integrity that we've exposed for your use: ```python def state_at_idx(self, idx: int) -> bool: """ Returns the state of the digital channel just before the given transition_time index """ ``` This one in particular may come in handy: ```python def state_at_time(self, time: float) -> bool: """ Returns the state of the digital channel at the given time """ ``` ### Re-Loading Previously Saved Captures The `Capture` class provides a method for creating a `Capture` instance exactly as it was when first captured. Call the following to re-load both the configuration and data files: ```{code-cell} python from pathlib import Path from saleae.mso_api import Capture capture = Capture.from_dir(Path("../my-capture")) print(f"Re-loaded capture with channels: {capture.analog_channel_names}") ``` ## 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_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_CH_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") ``` ## API Reference This section provides more details on each of the Saleae MSO API modules. ### Modules The API consists of the following main modules: - [MSO](mso.md): Manages interaction with the Logic MSO device - [Capture Configuration](capture_config.md): The configuration objects to control capturing from the device - [Capture](capture.md): The main object returned by a capture operation - [Trigger](trigger.md): Classes for defining trigger conditions - [Plot](plot.md): Simple debug plotting of captures - [Synthetic Trigger](synthetic_trigger.md): Tools for applying triggers to existing captures - [Binary Files](binary_files.md): Utilities for working with binary data files ## MSO The `MSO` class is the main interface for interacting with a Saleae Logic MSO device. It provides methods for capturing data and managing device settings. ### Overview The `MSO` class handles: - Device connection and initialization - Analog capture operations - Device configuration and settings ### Classes #### `MSO` ```python class MSO: def __init__(self, serial_number: Optional[str] = None) ``` **Parameters:** - `serial_number`: Optional serial number to connect to a specific device. If not provided, connects to the first available device. **Properties:** - `part_number`: The Saleae Part Number for the attached MSO. Supplies information like maximum sample rates and buffer sizes. ### Methods #### `MSO.capture()` ```python def capture(self, capture_config: mso_api.CaptureConfig, save_dir: pathlib.Path, timeout_secs: Optional[float] = None) -> mso_api.Capture ``` Captures data from the device according to the specified configuration. **Parameters:** - `capture_config`: Configuration for the capture (channels, sample rate, etc.) - `save_dir`: Directory to save capture data - `timeout_secs`: Optional timeout for the capture operation **Returns:** - A `Capture` object containing the captured data **Note:** This method will: 1. Create the save directory if it doesn't exist 2. Save the record options to `record_options.json` 3. Execute the capture command 4. Save the record result to `record_result.json` 5. Load the captured data into a `Capture` object **Example Usage:** ```{code-cell} python from pathlib import Path from saleae.mso_api import MSO, CaptureConfig, AnalogChannel, AnalogSettings, TimedCapture ## Initialize the MSO mso = MSO() ## Configure the capture config = CaptureConfig( enabled_channels=[AnalogChannel(channel=0, name="clock")], analog_settings=AnalogSettings(sample_rate=100e6), capture_settings=TimedCapture(capture_length_seconds=0.1), ) ## Perform the capture save_dir = Path("my-capture") capture = mso.capture(config, save_dir=save_dir) ## Access the captured data and inspect the actual (effective) capture settings print(f"Captured {capture.analog_data['clock'].num_samples} samples at a " f"{capture.config.analog_settings.sample_rate/1e6:.1f} MHz sample rate") ``` ### Error Handling The `MSO` class can raise the following exceptions, which are all subclasses of `MsoApiError`: - `CaptureTimeoutError` when capture operations timeout - `MsoCommandError` when device commands otherwise fail - `SmartCableNotReadyError` when a digital capture is initiated on a Smart Cable that's not plugged in - `SaleaeCommsProcessError` when something has broken in the low-level communications layer, or the device has been unplugged from USB during a capture. If either of these conditions arise, the directory you requested your capture to be saved in will contain a file called `traceback.txt` with debug information from your own program as well as the underlying capture executable. #### `CaptureTimeoutError` ```python class CaptureTimeoutError(MsoApiError): def __init__(self, timeout_seconds: Optional[float] = None): self.timeout_seconds = timeout_seconds ``` Exception raised when a capture operation times out. This can occur in two scenarios: - No trigger condition was found during a triggered capture - The actual capturing and uploading did not complete within the specified timeout period If you're running into a lot of these, try the following: - check that your trigger condition is actually occurring on the channel you've configured within the trigger timeout - consider increasing (or dropping) the overall command timeout (`timeout_secs`), as that includes upload time from the device. **Attributes:** - `timeout_seconds`: The timeout duration that was exceeded #### `MsoCommandError` ```python class MsoCommandError(MsoApiError): def __init__(self, command: str, stdout: str): self.command = command self.stdout = stdout super().__init__(f"Command {command} failed, stdout was: {stdout}") ``` Exception raised when device commands otherwise fail. **Attributes:** - `command`: The command that failed - `stdout`: The stdout output from the command #### `SmartCableNotReadyError` ```python class SmartCableNotReadyError(MsoApiError): def __init__(self, command: str, stdout: str): self.command = command self.stdout = stdout super().__init__(f"Smart cable was not connected or initialized in time. {command} failed, stdout was: {stdout}") ``` Exception raised when a smart cable is not ready or not connected. This typically occurs when: - The smart cable is not properly connected to the MSO device - The smart cable has not been initialized in time for the capture operation - There are communication issues with the smart cable hardware **Attributes:** - `command`: The command that failed - `stdout`: The stdout output from the command **Common Solutions:** - Ensure the smart cable is properly connected to the MSO device - Try unplugging and replugging the smart cable - Verify that the smart cable is being detected by the system by opening Logic2 and attempting to capture data from it #### `SaleaeCommsProcessError` ```python class SaleaeCommsProcessError(MsoApiError): def __init__(self, args: list[str], stdout: str, stderr: str, returncode: int, env: Optional[dict[str, str]] = None): self.msoexe_args = args self.stdout = stdout self.stderr = stderr self.returncode = returncode self.env = env ``` Exception raised when there are low-level communication issues with the MSO device. This typically occurs when: - The device is disconnected from USB during a capture operation - There are fundamental communication problems with the device **Attributes:** - `msoexe_args`: The command arguments that were passed to the MSO executable - `stdout`: The stdout output from the command - `stderr`: The stderr output from the command - `returncode`: The return code from the MSO executable - `env`: The environment variables used during execution **Note:** If you encounter this error outside of obvious scenarios like disconnecting the USB cable during capture, please contact Saleae support with the error details and your system information. We'd like to get the problem fixed as soon as possible. ## Plot Utility The plot utility provides a simple way to visualize analog and digital signals from Logic MSO captures. This utility is primarily intended for debugging and quick visualization purposes - it is not meant to be a fully-featured plotting solution. For advanced plotting needs, consider using matplotlib directly or other specialized plotting libraries. ### Features - Plot analog signals with voltage levels and trigger thresholds - Plot digital signals below, one per plot - Slice the time window for the plot as desired - Add vertical lines to mark things in time ### Usage ```python from pathlib import Path from saleae import mso_api from saleae.mso_api.utils.plot import plot_capture, PlotMarker ## Load a capture cap_dir = Path("path/to/capture") capture = mso_api.Capture.from_dir(cap_dir) ## Optional: Create markers for important events markers = [ PlotMarker(time=0.001), PlotMarker(time=0.002) ] ## Plot the capture plot_capture( capture=capture, save_to=Path("debug_plot.png"), start_time=0.000, # Optional: Start time in seconds stop_time=0.005, # Optional: Stop time in seconds title="My Capture", # Optional: Plot title markers=markers # Optional: List of markers ) ``` ### Parameters - `capture` (Capture): The capture object to plot - `save_to` (Path): Where to save the plot image - `start_time` (float, optional): Start time in seconds for the plot window - `stop_time` (float, optional): Stop time in seconds for the plot window - `title` (str, optional): Title for the plot - `markers` (list[PlotMarker], optional): List of markers to show on the plot ## Synthetic Trigger The `synthetic_trigger` module provides functionality to apply trigger conditions to existing captures, allowing you to split a single capture into multiple segments based on trigger conditions. ### Overview This module is particularly useful when you have a long capture and want to extract specific events based on signal characteristics. It allows you to: - Define trigger conditions after the capture has been made - Split a capture into multiple segments based on those triggers - Extract precise time windows around each trigger event - Apply holdoff periods between triggers ### Functions #### `split_capture` ```python def split_capture(capture: Capture, trigger: EdgeTrigger, pre_trigger_seconds: float, post_trigger_seconds: float) -> List[Capture] ``` Splits a capture into multiple captures based on a trigger condition. **Parameters:** - `capture`: The capture to split - `trigger`: The trigger settings (e.g., EdgeTrigger) - `pre_trigger_seconds`: Time before the trigger to include (can be negative to delay the trigger) - `post_trigger_seconds`: Time after the trigger to include (positive value) **Returns:** - A list of new captures, each spanning from a detected trigger - pre_trigger_seconds to the detected trigger + post_trigger_seconds **Note:** Captures that aren't long enough to include both pre- and post-trigger data are discarded. **Example:** ```python from saleae.mso_api.capture import Capture from saleae.mso_api.synthetic_trigger import split_capture from saleae.mso_api.trigger import EdgeTrigger, EdgeTriggerDirection from pathlib import Path ## Load a capture cap_dir = Path("path/to/capture") cap = Capture.from_dir(cap_dir) ## Define a trigger (rising edge on channel "data") trigger = EdgeTrigger( channel_name="data", threshold_volts=0, direction=EdgeTriggerDirection.RISING, holdoff_seconds=0.001 # 1ms minimum between triggers ) ## Split the capture with 6µs before the trigger and 56µs after caps = split_capture(cap, trigger, pre_trigger_seconds=-6e-6, post_trigger_seconds=56e-6) print(f"Found {len(caps)} triggered segments") ``` #### `find_trigger_indices` ```python def find_trigger_indices(capture: Capture, trigger: EdgeTrigger) -> List[int] ``` Finds the indices in the capture where the trigger conditions are met. **Parameters:** - `capture`: The capture to analyze - `trigger`: The trigger settings **Returns:** - A list of indices where the trigger conditions are met #### `slice_capture` ```python def slice_capture(capture: Capture, start_idx: int, end_idx: int) -> Capture ``` Creates a new capture containing only the data between the specified indices. **Parameters:** - `capture`: The capture to slice - `start_idx`: Starting index (inclusive) - `end_idx`: Ending index (exclusive) **Returns:** - A new Capture object containing only the specified range of data ### Common Use Cases 1. **Protocol Analysis**: Extract all instances of a specific protocol transaction 2. **Fault Detection**: Identify all occurrences of a fault condition in a long capture 3. **Signal Processing**: Pre-process data by extracting only relevant segments for further analysis ### Error Handling The module will raise appropriate errors if: - The trigger channel is not found in the capture - The capture has no channels - The pre-trigger time is after the post-trigger time - The capture segments are too short to include the requested time windows ## Trigger The `trigger` module provides classes for defining trigger conditions that can be used with both hardware captures and synthetic triggers. ### Overview This module defines the trigger settings that determine when a capture should start or how a capture should be split. It currently supports edge triggers, which detect when a signal crosses a specified voltage threshold. ### Classes #### `EdgeTriggerDirection` An enumeration that defines the direction of an edge trigger: ```python class EdgeTriggerDirection(enum.Enum): RISING = "rising" # Trigger when signal rises above threshold FALLING = "falling" # Trigger when signal falls below threshold ``` #### `EdgeTrigger` A dataclass that defines an edge trigger configuration: ```python @dataclass class EdgeTrigger: channel_index: Optional[int] = None channel_name: Optional[str] = None threshold_volts: float = 0.0 direction: EdgeTriggerDirection = EdgeTriggerDirection.RISING ``` **Parameters:** - `channel_index`: The channel number to trigger on (0-based index) - `channel_name`: The name of the channel to trigger on - `threshold_volts`: The voltage threshold that the signal must cross - `direction`: Whether to trigger on rising or falling edges - `holdoff`: Support coming soon! **Note:** Either `channel_index` or `channel_name` must be provided, but not both. If both are provided, a warning will be logged. ### Example Usage ```{code-cell} python from saleae import mso_api ## Create a trigger for rising edges on channel 1 at 0V trigger = mso_api.EdgeTrigger( channel_index=1, threshold_volts=0, direction=mso_api.EdgeTriggerDirection.RISING ) ``` ### Usage with Capture Configuration Edge triggers can also be used with the `CaptureConfig` class to configure hardware-triggered captures: ```{code-cell} python from saleae import mso_api ## Create a configuration for a triggered capture config = mso_api.CaptureConfig( enabled_channels=[mso_api.AnalogChannel(channel=0, name="data")], analog_settings=mso_api.AnalogSettings(sample_rate=100e6), capture_settings=mso_api.AnalogTriggered( trigger=mso_api.EdgeTrigger(channel_name="data", threshold_volts=0), pre_trigger_seconds=0.0005, post_trigger_seconds=0.0005, timeout_seconds=1.0 ) ) ``` ## API Reference ### saleae.mso_api Saleae MSO API package. #### `MSO` **Attributes:** | Name | Type | Description | |------|------|-------------| | `wrapper` | | | | `part_number` | `MsoPodPartNumber` | | **Methods:** ##### `__init__(serial_number: Optional[str] = None, wrapper: Optional[MsoWrapper] = None)` ##### `capture(capture_config: CaptureConfig, save_dir: Path, timeout_secs: Optional[float] = None, debug: bool = False) -> Capture` ##### `capture_analog(capture_config: CaptureConfig, save_dir: Path, timeout_secs: Optional[float] = None) -> Capture` Older alias for capture() ##### `close()` #### `AnalogData` **Attributes:** | Name | Type | Description | |------|------|-------------| | `voltages` | `NDArray[np.float32]` | | | `num_samples` | `int` | | **Methods:** ##### `voltage_at_time(time: float) -> float` Returns the voltage of the analog channel at the given time ##### `__init__(channel: int, name: str, begin_time: float, end_time: float, sample_rate: float, downsample: int, voltages: NDArray[np.float32]) -> None` #### `DiscontinuousAnalogData` **Attributes:** | Name | Type | Description | |------|------|-------------| | `channel` | `int` | | | `name` | `str` | | | `begin_time` | `float` | | | `end_time` | `float` | | | `sample_rate` | `float` | | | `downsample` | `int` | | | `traces` | `List[AnalogData]` | | | `num_samples` | `int` | | **Methods:** ##### `voltage_at_time(time: float) -> float` Returns the voltage of the analog channel at the given time ##### `__init__(channel: int, name: str, begin_time: float, end_time: float, sample_rate: float, downsample: int, traces: List[AnalogData]) -> None` #### `DigitalData` **Attributes:** | Name | Type | Description | |------|------|-------------| | `initial_state` | `bool` | | | `transition_times` | `NDArray[np.float64]` | | **Methods:** ##### `state_at_idx(idx: int) -> bool` Returns the state of the digital channel just before the given transition_time index ##### `state_at_time(time: float) -> bool` Returns the state of the digital channel at the given time ##### `__init__(port: int, channel: int, name: str, begin_time: float, end_time: float, initial_state: bool, transition_times: NDArray[np.float64]) -> None` #### `DiscontinuousDigitalData` **Attributes:** | Name | Type | Description | |------|------|-------------| | `port` | `int` | | | `channel` | `int` | | | `name` | `str` | | | `begin_time` | `float` | | | `end_time` | `float` | | | `segments` | `List[DigitalData]` | | **Methods:** ##### `state_at_time(time: float) -> bool` Returns the state of the digital channel at the given time ##### `__init__(port: int, channel: int, name: str, begin_time: float, end_time: float, segments: List[DigitalData]) -> None` #### `Capture` **Attributes:** | Name | Type | Description | |------|------|-------------| | `analog_data` | `Dict[str, BaseAnalogData]` | | | `digital_data` | `Dict[str, BaseDigitalData]` | | | `config` | `CaptureConfig` | | | `analog_start_time` | `float` | Returns the start time of the capture. | | `analog_sample_rate` | `float` | Returns the sample rate of the capture. | | `analog_channel_names` | `List[str]` | Returns a list of analog channel names in the capture. | | `analog_time_series` | `NDArray[np.float64]` | Returns the time array for analog data. | | `analog_stop_time` | `float` | Returns the end time of the capture. | | `num_analog_samples` | `int` | Returns the number of analog samples in the capture. | **Methods:** ##### @classmethod `from_dir(dir_path: Path) -> Capture` Create a Capture from a directory containing analog_.bin files. Args: dir_path: Path to directory containing analog_.bin files Returns: Capture object with analog data loaded from files ##### @classmethod `from_config(config: CaptureConfig, dir_path: Path) -> Capture` Create a Capture from a CaptureConfig by reading the files in it's save directory ##### `delete()` Delete the capture files from the disk ##### `__init__(analog_data: Dict[str, BaseAnalogData], digital_data: Dict[str, BaseDigitalData], config: CaptureConfig, _analog_start_time: Optional[float] = None, _stored_path: Optional[Path] = None) -> None` #### `Coupling` Coupling type **Attributes:** | Name | Type | Description | |------|------|-------------| | `DC` | | | | `AC` | | | #### `ProbeAttenuation` Probe attenuation type **Attributes:** | Name | Type | Description | |------|------|-------------| | `PROBE_10X` | | | | `PROBE_1X` | | | #### `BandwidthLimit` Bandwidth limit type **Attributes:** | Name | Type | Description | |------|------|-------------| | `MHz_20` | | | | `MHz_350` | | | #### `DownsampleMode` Downsample mode type **Attributes:** | Name | Type | Description | |------|------|-------------| | `DROP` | | | | `AVERAGE` | | | #### `AnalogChannel` Analog channel configuration **Attributes:** | Name | Type | Description | |------|------|-------------| | `center_voltage` | `float` | | | `voltage_range` | `float` | | | `probe_attenuation` | `ProbeAttenuation` | | | `coupling` | `Coupling` | | | `bandwidth_mhz` | `BandwidthLimit` | | **Methods:** ##### `to_dict(complete: bool = False) -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> AnalogChannel` ##### `__init__(channel: int, name: Optional[str] = None, center_voltage: float = 0.0, voltage_range: float = 10.0, probe_attenuation: ProbeAttenuation = ProbeAttenuation.PROBE_10X, coupling: Coupling = Coupling.DC, bandwidth_mhz: BandwidthLimit = BandwidthLimit.MHz_350) -> None` #### `DigitalChannel` Digital channel configuration **Attributes:** | Name | Type | Description | |------|------|-------------| | `port` | `Optional[int]` | | | `threshold_volts` | `Optional[float]` | | | `minimum_pulse_width_samples` | `Optional[int]` | | **Methods:** ##### `to_dict(complete: bool = False) -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> DigitalChannel` ##### `__init__(channel: int, name: Optional[str] = None, port: Optional[int] = None, threshold_volts: Optional[float] = None, minimum_pulse_width_samples: Optional[int] = None) -> None` #### `AnalogSettings` Analog settings **Attributes:** | Name | Type | Description | |------|------|-------------| | `downsample_mode` | `DownsampleMode` | | | `sample_rate` | `float` | | **Methods:** ##### @classmethod `default_for(part_number: MsoPodPartNumber) -> AnalogSettings` ##### `validate_and_update_sample_rate_for(part_number: MsoPodPartNumber) -> int` Validate the sample rate is supported by the given device part number, updating slightly to the nearest valid rate if necessary Return the exact downsample ratio that will be used. ##### `to_dict() -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> AnalogSettings` ##### `__init__(downsample_mode: DownsampleMode = DownsampleMode.DROP, sample_rate: float = 1600000000) -> None` #### `TimedCapture` Settings for a timed capture **Attributes:** | Name | Type | Description | |------|------|-------------| | `capture_length_seconds` | `float` | | **Methods:** ##### `to_dict() -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> TimedCapture` ##### `__init__(capture_length_seconds: float) -> None` #### `AnalogTriggered` Settings for a capture triggered by an analog signal **Attributes:** | Name | Type | Description | |------|------|-------------| | `trigger` | `EdgeTrigger` | | | `pre_trigger_seconds` | `float` | | | `post_trigger_seconds` | `float` | | | `timeout_seconds` | `float` | | **Methods:** ##### `to_dict() -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> AnalogTriggered` ##### `__init__(trigger: EdgeTrigger, pre_trigger_seconds: float = 0.0005, post_trigger_seconds: float = 0.0005, timeout_seconds: float = 1.0) -> None` #### `CaptureConfig` Capture configuration **Attributes:** | Name | Type | Description | |------|------|-------------| | `enabled_channels` | `list[ChannelConfig]` | | | `analog_settings` | `Optional[AnalogSettings]` | | | `capture_settings` | `Optional[CaptureSettings]` | | **Methods:** ##### `validate_sample_buffer_and_timeout(mso_part_number: MsoPodPartNumber)` Validate the timeout on the capture is possible given the pre_trigger_ and post_trigger_seconds, and that the maximum sample buffer depth is not exceeded. ##### `get_logic_analyzer_settings() -> Optional[json_types.LogicAnalyzerSettings]` Validate that the digital channels are configured correctly Returns a json_types.LogicAnalyzerSettings object or None if no digital channels are enabled ##### `get_analog_settings(part_number: MsoPodPartNumber) -> Optional[json_types.AnalogSettings]` Return the analog settings as a json_types.AnalogSettings object Return None if no analog channels are enabled ##### `find_trigger_channel(trigger: EdgeTrigger) -> ChannelConfig` Return the ChannelConfig from enabled_channels that matches the given trigger ##### `validate_enabled_channels() -> list[Dict[str, Any]]` Validate enabled_channels and convert them to dictionaries ##### `to_dict(output_dir: Path, mso_part_number: MsoPodPartNumber) -> Dict[str, Any]` Convert the configuration to a dictionary format expected by the MSO ##### `to_config_dict() -> dict[Any, Any]` Emit this configuration as a dictionary ##### @classmethod `from_config_dict(dict_version: dict[Any, Any]) -> CaptureConfig` Convert a dictionary version of this configuration to a CaptureConfig ##### `to_dir(output_dir: Path, mso_part_number: MsoPodPartNumber) -> Path` Save this configuration as a directory of files and return the path of the record options file ##### @classmethod `from_dir(dir_path: Path) -> CaptureConfig` Load a CaptureConfig from a directory of files ##### `__init__(enabled_channels: list[ChannelConfig], analog_settings: Optional[AnalogSettings] = None, capture_settings: Optional[CaptureSettings] = None) -> None` #### `EdgeTriggerDirection` Trigger direction **Attributes:** | Name | Type | Description | |------|------|-------------| | `RISING` | | | | `FALLING` | | | #### `EdgeTrigger` Trigger settings object **Attributes:** | Name | Type | Description | |------|------|-------------| | `channel_index` | `Optional[int]` | | | `channel_name` | `Optional[str]` | | | `threshold_volts` | `float` | | | `direction` | `EdgeTriggerDirection` | | | `holdoff_seconds` | `float` | | **Methods:** ##### `to_dict() -> Dict[str, Any]` ##### @classmethod `from_dict(dict_version: Dict[str, Any]) -> EdgeTrigger` ##### `__init__(channel_index: Optional[int] = None, channel_name: Optional[str] = None, threshold_volts: float = 0.0, direction: EdgeTriggerDirection = EdgeTriggerDirection.RISING, holdoff_seconds: float = 0.0) -> None` ### saleae.mso_api.utils Utility classes for binary file parsing and synthetic triggers. #### `SaleaeFileVersion` Enum representing Saleae file versions. **Attributes:** | Name | Type | Description | |------|------|-------------| | `FileVersion_Initial` | | | | `FileVersion_V1` | | | #### `SaleaeFiletype` Enum representing Saleae file types. **Attributes:** | Name | Type | Description | |------|------|-------------| | `Filetype_DigitalExport` | | | | `Filetype_AnalogExport` | | | | `Filetype_WaveformAdcExport` | | | | `Filetype_DigitalStore` | | | | `Filetype_AnalogStore` | | | #### `GraphTime` Represents a time value in the Saleae file format. **Attributes:** | Name | Type | Description | |------|------|-------------| | `milliseconds` | `int` | | | `fractional_milliseconds` | `float` | | **Methods:** ##### `__init__(milliseconds: int, fractional_milliseconds: float) -> None` #### `SamplingConfiguration` Configuration for sampling in Saleae files. **Attributes:** | Name | Type | Description | |------|------|-------------| | `sampling_rate` | `float` | | | `time_zero` | `GraphTime` | | **Methods:** ##### `__init__(sampling_rate: float, time_zero: GraphTime) -> None` #### `WaveformAdcExport_V0` Represents a WaveformAdcExport in Saleae binary format **Attributes:** | Name | Type | Description | |------|------|-------------| | `sample_rate` | `int` | | | `gain` | `float` | | | `offset` | `float` | | | `min_adc_code` | `int` | | | `max_adc_code` | `int` | | | `num_samples` | `int` | | | `adc_codes` | `NDArray[np.int16]` | | **Methods:** ##### `to_voltages() -> NDArray[np.float64]` Convert ADC codes to voltage values using gain and offset. ##### `__init__(sample_rate: int, gain: float, offset: float, min_adc_code: int, max_adc_code: int, num_samples: int, adc_codes: NDArray[np.int16]) -> None` #### `AnalogExport_V0` Represents an AnalogExport in Saleae binary format **Attributes:** | Name | Type | Description | |------|------|-------------| | `begin_time` | `float` | | | `sample_rate` | `float` | | | `downsample` | `int` | | | `num_samples` | `int` | | | `voltages` | `NDArray[np.float32]` | | **Methods:** ##### `__init__(begin_time: float, sample_rate: float, downsample: int, num_samples: int, voltages: NDArray[np.float32]) -> None` #### `DigitalExport_V0` Represents a DigitalExport in Saleae binary format. **Attributes:** | Name | Type | Description | |------|------|-------------| | `initial_state` | `int` | | | `begin_time` | `float` | | | `end_time` | `float` | | | `num_transitions` | `int` | | | `transition_times` | `NDArray[np.float64]` | | **Methods:** ##### `__init__(initial_state: int, begin_time: float, end_time: float, num_transitions: int, transition_times: NDArray[np.float64]) -> None` #### `DigitalChunk` Represents a chunk of digital data in v1 format. **Attributes:** | Name | Type | Description | |------|------|-------------| | `initial_state` | `int` | | | `sample_rate` | `float` | | | `begin_time` | `float` | | | `end_time` | `float` | | | `transition_times` | `NDArray[np.float64]` | | **Methods:** ##### `__init__(initial_state: int, sample_rate: float, begin_time: float, end_time: float, transition_times: NDArray[np.float64]) -> None` #### `AnalogWaveform` Represents a waveform of analog data in v1 format. **Attributes:** | Name | Type | Description | |------|------|-------------| | `begin_time` | `float` | | | `trigger_time` | `float` | | | `sample_rate` | `float` | | | `downsample` | `int` | | | `voltages` | `NDArray[np.float32]` | | **Methods:** ##### `__init__(begin_time: float, trigger_time: float, sample_rate: float, downsample: int, voltages: NDArray[np.float32]) -> None` #### `WaveformAdcWaveform` Represents a waveform of ADC data in v1 format. **Attributes:** | Name | Type | Description | |------|------|-------------| | `begin_time` | `float` | | | `trigger_time` | `float` | | | `sample_rate` | `float` | | | `downsample` | `int` | | | `gain` | `float` | | | `offset` | `float` | | | `min_adc_code` | `int` | | | `max_adc_code` | `int` | | | `adc_codes` | `NDArray[np.int16]` | | **Methods:** ##### `to_voltages() -> NDArray[np.float64]` Convert ADC codes to voltage values using gain and offset. ##### `__init__(begin_time: float, trigger_time: float, sample_rate: float, downsample: int, gain: float, offset: float, min_adc_code: int, max_adc_code: int, adc_codes: NDArray[np.int16]) -> None` #### `DigitalExport_V1` Represents a DigitalExport in Saleae binary format v1. **Attributes:** | Name | Type | Description | |------|------|-------------| | `chunks` | `list[DigitalChunk]` | | **Methods:** ##### `__init__(chunks: list[DigitalChunk]) -> None` #### `AnalogExport_V1` Represents an AnalogExport in Saleae binary format v1. **Attributes:** | Name | Type | Description | |------|------|-------------| | `waveforms` | `list[AnalogWaveform]` | | **Methods:** ##### `__init__(waveforms: list[AnalogWaveform]) -> None` #### `WaveformAdcExport_V1` Represents a WaveformAdcExport in Saleae binary format v1. **Attributes:** | Name | Type | Description | |------|------|-------------| | `waveforms` | `list[WaveformAdcWaveform]` | | **Methods:** ##### `__init__(waveforms: list[WaveformAdcWaveform]) -> None` #### `SaleaeFile` Represents a Saleae binary file with its metadata and data. **Attributes:** | Name | Type | Description | |------|------|-------------| | `token` | `str` | | | `version` | `SaleaeFileVersion` | | | `type` | `SaleaeFiletype` | | | `contents` | `Union[WaveformAdcExport_V0, WaveformAdcExport_V1, AnalogExport_V0, AnalogExport_V1, DigitalExport_V0, DigitalExport_V1, None]` | | **Methods:** ##### `__init__(token: str, version: SaleaeFileVersion, type: SaleaeFiletype, contents: Union[WaveformAdcExport_V0, WaveformAdcExport_V1, AnalogExport_V0, AnalogExport_V1, DigitalExport_V0, DigitalExport_V1, None] = None) -> None` ### saleae.mso_api.errors Error types raised by the Saleae MSO API. #### `MsoApiError` Base class for all exceptions in the Saleae MSO API #### `MsoCommandError` Exception raised when a command fails **Attributes:** | Name | Type | Description | |------|------|-------------| | `command` | | | | `stdout` | | | **Methods:** ##### `__init__(command: str, stdout: str)` #### `NoResultFileError` Exception raised when a command does not produce a result file **Methods:** ##### `__init__(command: str, stdout: str)` #### `SmartCableNotReadyError` Exception raised when a smart cable is not ready or not connected **Methods:** ##### `__init__(command: str, stdout: str)` #### `CaptureTimeoutError` Exception raised when a capture times out **Attributes:** | Name | Type | Description | |------|------|-------------| | `record_options` | `Optional[dict[Any, Any]]` | | | `timeout_seconds` | | | **Methods:** ##### `__init__(record_options: dict[Any, Any], timeout_seconds: Optional[float] = None)` #### `CaptureConfigError` Error for invalid capture configurations #### `SaleaeCommsProcessError` **Attributes:** | Name | Type | Description | |------|------|-------------| | `msoexe_args` | | | | `stdout` | | | | `stderr` | | | | `returncode` | | | | `env` | | | **Methods:** ##### `__init__(args: list[str], stdout: str, stderr: str, returncode: int, env: Optional[dict[str, str]] = None)` #### `NoJsonFound` #### `USBPermissionsError` Exception raised when a USB device is not accessible due to permissions on a Linux system **Methods:** ##### `__init__()` #### `Timeout` #### `TimeOutOfBoundsError` Exception raised when a time is not defined #### `UndefinedDataError` Exception raised when data is not defined for the given time period