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#

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()#

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:

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")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], line 16
     14 # Perform the capture
     15 save_dir = Path("my-capture")
---> 16 capture = mso.capture(config, save_dir=save_dir)
     18 # Access the captured data and inspect the actual (effective) capture settings
     19 print(f"Captured {capture.analog_data['clock'].num_samples} samples at a "
     20       f"{capture.config.analog_settings.sample_rate/1e6:.1f} MHz sample rate")

File ~\src\mso-api\src\saleae\mso_api\mso_device.py:103, in MSO.capture(self, capture_config, save_dir, timeout_secs, debug)
    100     record_traceback(save_dir, self.wrapper.last_stdout or "", exception_tb, exception)
    102 # read the data into numpy arrays
--> 103 return Capture.from_config(capture_config, save_dir)

File ~\src\mso-api\src\saleae\mso_api\capture.py:506, in Capture.from_config(cls, config, dir_path)
    502 """
    503 Create a Capture from a CaptureConfig by reading the files in it's save directory
    504 """
    505 dir_path = Path(dir_path)
--> 506 capture_obj = Capture.from_dir(dir_path)
    508 # TODO: do some sanity checking comparing the recovered config with the provided config
    510 capture_obj.config = copy.deepcopy(config)

File ~\src\mso-api\src\saleae\mso_api\capture.py:497, in Capture.from_dir(cls, dir_path)
    490 capture_obj = cls(
    491     analog_data={},
    492     digital_data={},
    493     config=config,
    494     _stored_path=dir_path,
    495 )
    496 # load the data from the files
--> 497 capture_obj._load_data_from_located_files(located_files)
    498 return capture_obj

File ~\src\mso-api\src\saleae\mso_api\capture.py:460, in Capture._load_data_from_located_files(self, located_files)
    458 for located_file in located_files:
    459     if isinstance(located_file.channel_config, AnalogChannel):
--> 460         self._load_analog_data_file(located_file)
    461     elif isinstance(located_file.channel_config, DigitalChannel):
    462         self._load_digital_data_file(located_file)

File ~\src\mso-api\src\saleae\mso_api\capture.py:309, in Capture._load_analog_data_file(self, located_file)
    307     self._load_analog_data_file_v0(located_file, parsed_file.contents)
    308 elif isinstance(parsed_file.contents, binary_files.AnalogExport_V1):
--> 309     self._load_analog_data_file_v1(located_file, parsed_file.contents)
    310 else:
    311     raise NotImplementedError("Only AnalogExport_V0 and AnalogExport_V1 are supported at this time")

File ~\src\mso-api\src\saleae\mso_api\capture.py:348, in Capture._load_analog_data_file_v1(self, located_file, contents)
    346 # TODO: update the epsilon on this check if/when export sample rate starts being defined as a double
    347 elif abs(self.config.analog_settings.sample_rate - float(contents.waveforms[0].sample_rate)) > 1.0:
--> 348     raise ValueError(f"Inconsistent sample rates: {self.config.analog_settings.sample_rate} vs {contents.waveforms[0].sample_rate}")
    350 # track start time
    351 if self._analog_start_time is None:

ValueError: Inconsistent sample rates: 100000000.0 vs 125000000.0

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#

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#

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#

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#

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.