Hello!
First, if you haven’t seen the recent release of headless logic2-automation support, you can check it out here: Headless logic2-automation Support
I’m excited to announce the first preview release of logic2-automation support for Logic MSO!
This is an early release, and much is subject to change. We would love to get as much feedback as possible!
This expands our existing logic2-automation API to support Logic MSO. This will eventually replace the existing mso_api.
This adds the ability to take captures with Logic MSO, using free-run, timer, and trigger modes (both digital and analog triggers) with the scope inputs as well as the logic analyzer probes.
Python Downloads
Note: We don’t provide an Windows-arm64 python Library because a pre-built gRPC library is not available for that platform. There are workarounds for this.
- Windows (x64) logic2_automation-1.2.0-py3-none-win_amd64.whl
- Windows (arm64) not available
- macOS (Intel) logic2_automation-1.2.0-py3-none-macosx_10_14_x86_64.whl
- macOS (Apple Silicon) logic2_automation-1.2.0-py3-none-macosx_11_0_arm64.whl
- Linux (x64) logic2_automation-1.2.0-py3-none-manylinux_2_27_x86_64.whl
- Linux (arm64) logic2_automation-1.2.0-py3-none-manylinux_2_27_aarch64.whl
Server Binary Downloads
- Windows (x64) logic_automation_server-windows-x64.zip
- Windows (arm64) logic_automation_server-windows-arm64.zip
- macOS (Intel) logic_automation_server-macos-x64.zip
- macOS (Apple Silicon) logic_automation_server-macos-arm64.zip
- Linux (x64) logic_automation_server-linux-x64.zip
- Linux (arm64) logic_automation_server-linux-arm64.zip
Requirements
- Windows: you need to have installed our drivers first. These come with the Logic 2 software.
- Linux: you need to install the udev rules file first. Our software normally helps you with this.
Documentation
Coming soon! Please see the example file and the python doc strings.
Our existing automation documentation can be found here, however it does not cover the headless automation server or Logic MSO yet: Getting Started - Saleae API Documentation
Getting Started Sample
# save the sample below as example.py
# I would recommend doing this in a virtual environment.
# install the wheel file you just downloaded. This is the windows x64 one:
pip install logic2_automation-1.2.0-py3-none-win_amd64.whl
python example.py
from saleae import automation
import grpc
import os
import os.path
import time
from datetime import datetime
# Example of using Logic MSO with the headless logic2-automation server.
# This example runs through three different captures: a timed capture, an analog triggered capture, and a digital triggered capture.
# It requires a Logic MSO connected to the system. if you have any logic analyzer probes connected to the MSO, those will be used too.
# For the analog trigger tests to work, you will need an analog signal connected to the first channel that passes 1.65 volts.
# For the digital trigger test to work, you will need a digital signal connected to either the first logic analyzer probe channel, or to the first analog channel if no probes are used.
def wait_or_timeout(capture, timeout_seconds: float) -> bool:
"""Wait for a capture to complete, giving up after timeout_seconds.
Returns True if the capture completed within the timeout.
This should be used with triggered captures to ensure that the script does not get stuck if the trigger is never found.
"""
try:
capture.wait(timeout_seconds=timeout_seconds)
return True
except grpc.RpcError as exc:
# Note, capture.wait just uses the gRPC deadline feature, so we expect a gRPC error if it times out.
if exc.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
return False
raise
# Note: only the headless server supports Logic MSO, so this example requires headless=True. It can't be used with the Logic 2 software.
with automation.Manager.launch(headless=True) as manager:
# Optional: Enable crash reporting and analytics to help Saleae catch issues, both hard crashes and unexpected behavior.
manager.set_reporting(analytics=True, crashes=True)
# manager.get_devices() returns all connected devices including Logic MSO.
# find the first connected Logic MSO.
mso = next((d for d in manager.get_devices()
if d.device_type == automation.DeviceType.LOGIC_MSO), None)
if mso is None:
raise RuntimeError('No Logic MSO detected')
# This is a workaround for an open issue where logic analyzer probes take 2 seconds to detect, per probe.
# This is on the list to fix, but in the meantime, this block of code will wait at least 3 seconds to detect
# any connected logic analyzer probes.
# Important: This is only necessary if you wish to dynamically detect connected probes.
# If you know which probes are connected, you can provide that in the device configuration,
# and the start_capture operation will block for them to be available.
probe_ports = {p.port_index for p in mso.mso_info.connected_probe_ports}
deadline = time.monotonic() + 3.0
while time.monotonic() < deadline:
time.sleep(0.2)
mso = next(d for d in manager.get_devices()
if d.device_id == mso.device_id)
ports = {p.port_index for p in mso.mso_info.connected_probe_ports}
if ports - probe_ports:
probe_ports = ports
deadline = time.monotonic() + 3.0
probe_ports = sorted(probe_ports)
print(f'Using Logic MSO {mso.device_id}, '
f'digital probes on ports: {probe_ports or "none"}')
# MSO device configuration has a few more options than our other devices.
# For each scope input, you can enable analog and/or digital recording.
# For analog recording, you also need to provide the input voltage range and other information.
# For digital recording of the analog channels, you need to provide the input low and input high digital thresholds. (not just a single threshold)
# These thresholds are per-channel.
device_configuration = automation.MsoDeviceConfiguration(
scope_channels=[
automation.MsoScopeChannelConfiguration(
index=0,
analog=automation.MsoScopeAnalogChannelConfiguration(
center_voltage=1.65,
voltage_range=10.0,
probe_attenuation=10.0,
),
digital=automation.MsoScopeDigitalChannelConfiguration(
threshold_high_voltage=2.0,
threshold_low_voltage=0.8,
),
),
],
# Free-run (manual), timed, and digital trigger captures require analog sample rates below 25 MS/s to stream the data.
# analog triggered captures use the device side hardware buffer, allowing the full sample rate of the device. (1.0 or 1.6 GS/s, depending on model.)
analog_sample_rate=1_000_000,
# configure any digital probes connected to the type-C ports on MSO.
digital_probes=[
automation.MsoDigitalProbeConfiguration(
port_index=port_index,
# unlike the digital scope channels above, logic analyzer probes take a single threshold that's shared across all 4 channels.
threshold_voltage=1.65,
channels=[
automation.MsoDigitalProbeChannelConfiguration(index=i)
for i in range(4)
],
)
for port_index in probe_ports
],
)
# Channel identification is a little more complicated than it was for our other devices.
# Instead of providing a list of integer channel indexes, each channel is a ScopeChannel (either digital or analog) or DigitalProbeChannel instance.
scope_analog = automation.ScopeChannel(
index=0, type=automation.ScopeChannelType.ANALOG)
scope_digital = automation.ScopeChannel(
index=0, type=automation.ScopeChannelType.DIGITAL)
probe_channels = [
automation.DigitalProbeChannel(port_index=port_index, index=i)
for port_index in probe_ports for i in range(4)
]
all_channels = [scope_analog, scope_digital] + probe_channels
# Store output in a timestamped directory, one subdirectory per capture.
output_dir = os.path.join(
os.getcwd(), f'output-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}')
os.makedirs(output_dir)
# ------------------------------------------------------------------
# Capture 1: timed capture - record 1 second of data, then stop.
# ------------------------------------------------------------------
capture_configuration = automation.CaptureConfiguration(
capture_mode=automation.TimedCaptureMode(duration_seconds=1.0)
)
with manager.start_capture(
device_id=mso.device_id,
device_configuration=device_configuration,
capture_configuration=capture_configuration) as capture:
capture.wait()
# There is one change to analyzers. When using analyzers with Logic MSO, you need to provide channel instances instead of an integer index for channel settings.
serial_input = probe_channels[0] if probe_channels else scope_digital
serial_analyzer = capture.add_analyzer(
'Async Serial', label='Test Analyzer', settings={
'Input Channel': serial_input,
'Bit Rate (Bits/s)': 115200,
})
timed_dir = os.path.join(output_dir, 'timed')
os.makedirs(timed_dir)
# Export analyzer data to a CSV file
capture.export_data_table(
filepath=os.path.join(timed_dir, 'serial_export.csv'),
analyzers=[serial_analyzer]
)
# Export raw data to CSV files (analog.csv and digital.csv)
capture.export_raw_data_csv(
directory=timed_dir, mso_channels=all_channels)
# Finally, save the capture to a file.
# These can be opened in Logic 2. However, because the capabilities of the API are slightly different from the Logic 2 software,
# You may notice some differences in the loaded capture. The sample rate and trigger settings might not match your settings.
capture.save_capture(
filepath=os.path.join(timed_dir, 'timed_capture.sal'))
print(f'Timed capture complete: {timed_dir}')
# ------------------------------------------------------------------
# Capture 2: analog trigger - single shot on a rising edge through
# 1.65V on scope channel 0, keeping 100 ms before and after the trigger.
# Analog-trigger captures may use the device's full sample rate range.
# ------------------------------------------------------------------
capture_configuration = automation.CaptureConfiguration(
capture_mode=automation.MsoTriggerCaptureMode(
trigger=automation.MsoAnalogTriggerConfiguration(
channel=scope_analog,
threshold_voltage=1.65,
trigger_type=automation.MsoAnalogTriggerEdgeType.RISING,
hysteresis_voltage=0.2,
),
pre_trigger_seconds=0.1,
post_trigger_seconds=0.1,
)
)
with manager.start_capture(
device_id=mso.device_id,
device_configuration=device_configuration,
capture_configuration=capture_configuration) as capture:
# Wait for the trigger, but give up after 5 seconds if the signal
# never produces one. An untriggered capture has no data to export.
if wait_or_timeout(capture, timeout_seconds=5.0):
analog_dir = os.path.join(output_dir, 'trigger_analog')
os.makedirs(analog_dir)
capture.export_raw_data_csv(
directory=analog_dir, mso_channels=all_channels)
capture.save_capture(
filepath=os.path.join(analog_dir, 'analog_trigger.sal'))
print(f'Analog trigger capture complete: {analog_dir}')
else:
capture.stop()
print('Analog trigger capture: no trigger within 5 seconds. This example requires a signal that passes 1.65 volts on scope channel 0 to trigger.')
# ------------------------------------------------------------------
# Capture 3: digital trigger - single shot on a rising edge on a digital
# probe channel (or the derived-digital scope channel if no probes are
# connected), keeping 100 ms before and after the trigger.
# ------------------------------------------------------------------
trigger_channel = probe_channels[0] if probe_channels else scope_digital
capture_configuration = automation.CaptureConfiguration(
capture_mode=automation.MsoTriggerCaptureMode(
trigger=automation.MsoDigitalTriggerConfiguration(
trigger_type=automation.DigitalTriggerType.RISING,
trigger_channel=trigger_channel,
# Not shown here: linked_channels and pulse width trigger settings.
),
pre_trigger_seconds=0.1,
post_trigger_seconds=0.1,
)
)
with manager.start_capture(
device_id=mso.device_id,
device_configuration=device_configuration,
capture_configuration=capture_configuration) as capture:
if wait_or_timeout(capture, timeout_seconds=5.0):
digital_dir = os.path.join(output_dir, 'trigger_digital')
os.makedirs(digital_dir)
capture.export_raw_data_csv(
directory=digital_dir, mso_channels=all_channels)
capture.save_capture(
filepath=os.path.join(digital_dir, 'digital_trigger.sal'))
print(f'Digital trigger capture complete: {digital_dir}')
else:
capture.stop()
print('Digital trigger capture: no trigger within 5 seconds. This example requires a digital signal that passes through a rising edge on the first logic analyzer probe channel (or the derived-digital scope channel if no probes are connected).')
print(f'Done. Output in {output_dir}')
Known issues:
- We’re planning to make breaking changes to this interface in the next few weeks, before we consider it “official.” You will likely need to update your implementations as these updates come out.
start_capturecan return before the device starts sampling.- Using logic analyzer probes can cause the capture to take longer to start (after calling
start_capture). - The saved *.sal files load into the logic 2 GUI, but their settings aren’t always accurately reflected. The automation API exposes capture options that aren’t available in the GUI. For example, you have a lot more flexibility when selecting an analog sample rate.
- Not really an issue, but digital triggered captures through the headless interface are structured differently than those in the GUI software. the GUI software does not stream analog like our other products do when using a digital trigger, instead once the trigger is found, it retrieves a high resolution analog trace from the hardware buffer. The headless automation implementation instead streams analog data in real time, providing a complete timeline of analog activity, at the cost of a severely limited analog sample rate.
- Logic Analyzer probes take 2 seconds each to detect, just like they do in the GUI software. get_devices does not block for them to connect, so they might not be shown in the get_devices response right away. However, the start_capture operation will block for smart cables to connect, if they were specified in the capture configuration.
- We’re releasing this considerably earlier than we normally ship features. We haven’t finished code review or merged this project, and we have a dozen more smaller open issues not worth listing here.
- We don’t have a story yet for dealing with firmware compatibility with Logic 2. Both Logic 2 and this headless automation server require the MSO firmware version to be an exact match for the expected version (unlike the older mso-api, which just just YOLOed it). This means a Logic 2 update could lead to a device firmware update that would then break compatibility with the headless automation server. The opposite is not possible as the headless automation server has no ability to update the device firmware on Logic MSO. I expect that as the firmware matures, we’ll be able to mainly do minor revision changes that won’t break compatibility very often. We we may also add support to the headless server to update the device firmware.
- Missing documentation. I’m working on it! In the short term, your best bets are the example script and the python doc strings.