
Logicmso
- 31 installs
- 805 repo stars
- Updated June 1, 2026
- brownfinesecurity/iothackbot
logicmso is a Claude skill that analyzes Saleae Logic MSO binary captures with the saleae-mso-api to decode UART, SPI, and I2C protocols for reverse engineering.
About
This skill analyzes digital and analog captures from Saleae Logic MSO devices using the saleae-mso-api Python library. A developer uses it to load binary exports, analyze signal transitions and pulse durations, and decode protocols like UART, SPI, and I2C. It is aimed at CTF challenges, hardware reverse engineering, and protocol decoding.
- Loads Saleae Logic MSO binary captures and analyzes signal transitions
- Decodes UART, SPI, I2C, and 1-Wire protocols from digital captures
- For CTF challenges and hardware reverse engineering
Logicmso by the numbers
- 31 all-time installs (skills.sh)
- Ranked #1,482 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Jul 31, 2026 (Skillselion catalog sync)
logicmso capabilities & compatibility
- Capabilities
- protocol decoding · signal analysis · uart decode · spi decode · i2c decode
- Use cases
- security audit
- Pricing
- Free
What logicmso says it does
Analyze digital and analog captures from Saleae Logic MSO devices. Decode protocols like UART, SPI, I2C from exported binary files.
This skill enables analysis of captured signals from Saleae Logic MSO devices using the `saleae-mso-api` Python library.
See [examples.md](examples.md) for full worked end-to-end captures: unknown-protocol triage, and UART, SPI, I2C, and 1-Wire decoding with runnable Python.
npx skills add https://github.com/brownfinesecurity/iothackbot --skill logicmsoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 805 |
| Last updated | June 1, 2026 |
| Repository | brownfinesecurity/iothackbot ↗ |
What it does
Decode UART, SPI, or I2C protocols from a Saleae Logic MSO capture for hardware reverse engineering or CTFs.
Who is it for?
Analyzing Saleae Logic MSO captures to decode UART, SPI, I2C, or 1-Wire for CTFs and hardware reverse engineering.
When should I use this skill?
Analyzing logic analyzer captures for CTF challenges, hardware reverse engineering, or protocol decoding.
What you get
Decoded protocol timing and messages from a Saleae Logic MSO binary capture.
By the numbers
- decodes 4 protocols in worked examples: UART, SPI, I2C, 1-Wire
- common UART baud rates 9600 to 115200
Files
Saleae Logic MSO Analysis
This skill enables analysis of captured signals from Saleae Logic MSO devices using the saleae-mso-api Python library. It supports loading binary exports, analyzing signal transitions, and decoding common protocols.
Prerequisites
saleae-mso-apiPython package — Do NOT blindly pip install. First check if it's already installed:
python3 -c "from saleae.mso_api.binary_files import read_file; print('saleae-mso-api is available')"Only if that fails, install it: pip install saleae-mso-api
- Binary export files from Saleae Logic software (
.binformat)
Quick Reference
Loading Binary Files
from saleae.mso_api.binary_files import read_file
from pathlib import Path
file_path = Path("capture.bin")
saleae_file = read_file(file_path)
# Access metadata
print(f"Version: {saleae_file.version}")
print(f"Type: {saleae_file.type}")
# Access data
contents = saleae_file.contentsDigital Capture Structure
Digital exports contain DigitalExport_V1 with chunks:
chunk = saleae_file.contents.chunks[0]
# Key attributes:
chunk.initial_state # Starting logic level (0 or 1)
chunk.transition_times # numpy array of transition timestamps (seconds)
chunk.sample_rate # Capture rate in Hz
chunk.begin_time # Capture start time
chunk.end_time # Capture end timeCalculating Pulse Durations
import numpy as np
times = np.array(chunk.transition_times)
durations_ms = np.diff(times) * 1000 # Convert to milliseconds
# If initial_state is 0 (LOW):
# - Even indices (0, 2, 4...) = HIGH pulse durations
# - Odd indices (1, 3, 5...) = LOW gap durations
# If initial_state is 1 (HIGH):
# - Even indices = LOW gap durations
# - Odd indices = HIGH pulse durationsHelper Scripts
This skill includes helper scripts for common analysis tasks:
Protocol Analyzer
# Analyze signal characteristics
python3 skills/logicmso/analyze_protocol.py capture.bin
# Show detailed timing histogram
python3 skills/logicmso/analyze_protocol.py capture.bin --histogram
# Show detected timing clusters
python3 skills/logicmso/analyze_protocol.py capture.bin --clusters
# Export transitions to CSV
python3 skills/logicmso/analyze_protocol.py capture.bin --export transitions.csv
# Show raw transition values
python3 skills/logicmso/analyze_protocol.py capture.bin --raw -n 50See examples.md for full worked end-to-end captures: unknown-protocol triage, and UART, SPI, I2C, and 1-Wire decoding with runnable Python.
Common Protocol Patterns
UART (Asynchronous Serial)
- Idle state: HIGH
- Start bit: LOW (1 bit period)
- Data bits: 8 bits, LSB first
- Stop bit: HIGH (1-2 bit periods)
- Common baud rates: 9600, 19200, 38400, 57600, 115200
- Bit period calculation:
1/baud_rateseconds - Identifying features: Consistent bit periods, durations are multiples of base period
SPI (Serial Peripheral Interface)
- 4 signals: SCLK (clock), MOSI (master out), MISO (master in), CS (chip select)
- Clock polarity (CPOL): Idle clock state (0=LOW, 1=HIGH)
- Clock phase (CPHA): Sample edge (0=leading, 1=trailing)
- Data: Sampled on clock edges, typically 8 bits per transaction
- Identifying features: Regular clock signal, CS goes LOW during transaction
I2C (Inter-Integrated Circuit)
- 2 signals: SDA (data), SCL (clock)
- Idle state: Both HIGH (pulled up)
- Start condition: SDA falls while SCL is HIGH
- Stop condition: SDA rises while SCL is HIGH
- Data: 8 bits + ACK/NACK, MSB first
- Address: 7-bit (first byte after START)
- Identifying features: START/STOP conditions, 9 clock pulses per byte (8 data + ACK)
1-Wire
- Single signal: DQ (data/power)
- Idle state: HIGH (pulled up)
- Reset pulse: Master pulls LOW for 480us minimum
- Presence pulse: Slave responds LOW for 60-240us
- Write 0: LOW for 60-120us
- Write 1: LOW for 1-15us, then release
- Read: Master samples 15us after pulling LOW
Analysis Workflow
Step 1: Initial Exploration
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("capture.bin")
chunk = f.contents.chunks[0]
print(f"Sample rate: {chunk.sample_rate/1e6:.1f} MHz")
print(f"Duration: {chunk.end_time - chunk.begin_time:.3f}s")
print(f"Initial state: {'HIGH' if chunk.initial_state else 'LOW'}")
print(f"Transitions: {len(chunk.transition_times)}")Step 2: Analyze Timing Patterns
times = np.array(chunk.transition_times)
durations_us = np.diff(times) * 1e6 # microseconds
# Separate HIGH and LOW durations
high_idx = 0 if chunk.initial_state == 0 else 1
high_durations = durations_us[high_idx::2]
low_durations = durations_us[(1-high_idx)::2]
print(f"HIGH pulses: min={min(high_durations):.1f}us, max={max(high_durations):.1f}us")
print(f"LOW gaps: min={min(low_durations):.1f}us, max={max(low_durations):.1f}us")
# Find unique timing values (cluster detection)
unique_high = sorted(set(round(d, -1) for d in high_durations)) # Round to 10us
unique_low = sorted(set(round(d, -1) for d in low_durations))
print(f"HIGH clusters: {unique_high}")
print(f"LOW clusters: {unique_low}")Step 3: Identify Protocol
Based on timing patterns:
- UART: Consistent bit periods, durations are multiples of base period, idles HIGH
- SPI/I2C: us-scale timing, needs clock signal analysis, look for regular patterns
- 1-Wire: Reset pulses ~480us, data pulses 1-120us
Step 4: Decode
Once protocol is identified, decode based on protocol rules. For unknown/custom protocols, analyze the timing clusters and bit patterns to determine encoding scheme.
UART Decoding Example
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("uart_capture.bin")
chunk = f.contents.chunks[0]
times = np.array(chunk.transition_times)
BAUD = 115200
BIT_PERIOD = 1 / BAUD
def decode_uart_byte(start_time, times, bit_period):
"""Decode a single UART byte starting at start_time."""
byte_val = 0
for bit_num in range(8):
# Sample at center of each bit (1.5, 2.5, 3.5... bit periods from start)
sample_time = start_time + (1.5 + bit_num) * bit_period
# Find state at sample_time
idx = np.searchsorted(times, sample_time)
state = (chunk.initial_state + idx) % 2
if state:
byte_val |= (1 << bit_num) # LSB first
return byte_val
# Find start bits (falling edges when idle HIGH)
decoded_bytes = []
i = 0
while i < len(times) - 1:
# Look for falling edge (start bit)
if chunk.initial_state == 1 or i > 0:
byte_val = decode_uart_byte(times[i], times, BIT_PERIOD)
decoded_bytes.append(byte_val)
# Skip to next potential start bit (after stop bit)
i += 1
while i < len(times) and times[i] < times[i-1] + 10 * BIT_PERIOD:
i += 1
else:
i += 1
print("Decoded:", bytes(decoded_bytes))CTF Tips
1. Unknown protocol: Start with analyze_protocol.py --clusters to see timing distribution 2. Multiple channels: Export each channel separately, identify clock vs data lines 3. Inverted signals: Some captures have inverted logic levels 4. Timing variations: Real hardware has jitter, use threshold-based detection 5. Partial captures: Check if capture starts mid-transmission 6. Custom protocols: Look for repeating patterns, identify sync/framing bytes
Troubleshooting
"No module named 'saleae.mso_api'"
First verify it's truly missing:
python3 -c "from saleae.mso_api.binary_files import read_file"Only if the import fails, install it:
pip install saleae-mso-apiEmpty or corrupt file
Check file size and try re-exporting from Saleae Logic software.
No transitions detected
- Signal may be constant (stuck high/low)
- Check if correct channel was exported
- Verify trigger settings in original capture
Timing seems wrong
- Check sample rate matches original capture settings
- Verify time units (seconds vs milliseconds vs microseconds)
#!/usr/bin/env python3
"""
Protocol Analyzer for Saleae Logic MSO captures.
Analyzes digital signal captures to identify timing patterns and help
determine the protocol being used.
"""
import argparse
import sys
from pathlib import Path
from collections import Counter
import numpy as np
try:
from saleae.mso_api.binary_files import read_file
except ImportError:
print("Error: saleae-mso-api not installed. Run: pip install saleae-mso-api")
sys.exit(1)
# Common baud rates and their bit periods in microseconds
COMMON_BAUD_RATES = {
300: 3333.33,
1200: 833.33,
2400: 416.67,
4800: 208.33,
9600: 104.17,
19200: 52.08,
38400: 26.04,
57600: 17.36,
115200: 8.68,
230400: 4.34,
460800: 2.17,
921600: 1.09,
}
def load_capture(file_path: Path) -> tuple:
"""Load a Saleae binary capture file and return transition data."""
saleae_file = read_file(file_path)
if not hasattr(saleae_file.contents, 'chunks') or len(saleae_file.contents.chunks) == 0:
raise ValueError("No digital data chunks found in file")
chunk = saleae_file.contents.chunks[0]
times = np.array(chunk.transition_times)
return {
'times': times,
'initial_state': chunk.initial_state,
'sample_rate': chunk.sample_rate,
'begin_time': chunk.begin_time,
'end_time': chunk.end_time,
}
def analyze_timing(data: dict) -> dict:
"""Analyze timing characteristics of the signal."""
times = data['times']
if len(times) < 2:
return {'error': 'Not enough transitions'}
durations_s = np.diff(times)
durations_us = durations_s * 1e6
durations_ms = durations_s * 1e3
# Separate HIGH and LOW durations
initial = data['initial_state']
high_idx = 0 if initial == 0 else 1
low_idx = 1 - high_idx
high_durations_us = durations_us[high_idx::2]
low_durations_us = durations_us[low_idx::2]
return {
'total_transitions': len(times),
'capture_duration_s': data['end_time'] - data['begin_time'],
'signal_duration_s': times[-1] - times[0] if len(times) > 0 else 0,
'initial_state': 'HIGH' if initial else 'LOW',
'all': {
'min_us': float(durations_us.min()),
'max_us': float(durations_us.max()),
'mean_us': float(durations_us.mean()),
'std_us': float(durations_us.std()),
},
'high': {
'count': len(high_durations_us),
'min_us': float(high_durations_us.min()) if len(high_durations_us) > 0 else 0,
'max_us': float(high_durations_us.max()) if len(high_durations_us) > 0 else 0,
'mean_us': float(high_durations_us.mean()) if len(high_durations_us) > 0 else 0,
},
'low': {
'count': len(low_durations_us),
'min_us': float(low_durations_us.min()) if len(low_durations_us) > 0 else 0,
'max_us': float(low_durations_us.max()) if len(low_durations_us) > 0 else 0,
'mean_us': float(low_durations_us.mean()) if len(low_durations_us) > 0 else 0,
},
'durations_us': durations_us,
'high_durations_us': high_durations_us,
'low_durations_us': low_durations_us,
}
def detect_clusters(durations_us: np.ndarray, tolerance: float = 0.15) -> list:
"""
Detect clusters of similar durations.
Returns list of (center_value, count) tuples.
"""
if len(durations_us) == 0:
return []
sorted_durations = np.sort(durations_us)
clusters = []
current_cluster = [sorted_durations[0]]
for dur in sorted_durations[1:]:
# Check if this duration is within tolerance of current cluster
cluster_mean = np.mean(current_cluster)
if abs(dur - cluster_mean) / cluster_mean <= tolerance:
current_cluster.append(dur)
else:
# Save current cluster and start new one
clusters.append((np.mean(current_cluster), len(current_cluster)))
current_cluster = [dur]
# Don't forget the last cluster
if current_cluster:
clusters.append((np.mean(current_cluster), len(current_cluster)))
# Sort by count (most common first)
clusters.sort(key=lambda x: -x[1])
return clusters
def guess_protocol(analysis: dict) -> list:
"""
Attempt to guess the protocol based on timing characteristics.
Returns list of (protocol_name, confidence, details) tuples.
"""
guesses = []
all_min = analysis['all']['min_us']
all_max = analysis['all']['max_us']
high_clusters = detect_clusters(analysis['high_durations_us'])
low_clusters = detect_clusters(analysis['low_durations_us'])
# Check for UART (look for consistent bit period)
for baud, period_us in COMMON_BAUD_RATES.items():
# Check if minimum duration is close to a baud rate bit period
if 0.7 < all_min / period_us < 1.3:
# Check if durations are multiples of the bit period
multiples = analysis['durations_us'] / period_us
rounded = np.round(multiples)
error = np.abs(multiples - rounded).mean()
if error < 0.15:
guesses.append((
f'UART ({baud} baud)',
max(0.3, 0.9 - error * 3),
f'Bit period ~{period_us:.1f}us'
))
# Check for 1-Wire (reset pulse ~480us, data pulses 1-120us)
if all_min < 20 and all_max > 400:
has_reset = any(400 < d < 600 for d in analysis['low_durations_us'])
has_short = any(d < 20 for d in analysis['durations_us'])
if has_reset and has_short:
guesses.append((
'1-Wire',
0.6,
'Detected reset pulses and short data pulses'
))
# Sort by confidence
guesses.sort(key=lambda x: -x[1])
return guesses
def print_histogram(durations_us: np.ndarray, bins: int = 20, title: str = "Duration Histogram"):
"""Print a simple ASCII histogram."""
if len(durations_us) == 0:
print(f"{title}: No data")
return
hist, edges = np.histogram(durations_us, bins=bins)
max_count = max(hist)
print(f"\n{title}")
print("=" * 60)
for i, count in enumerate(hist):
left = edges[i]
right = edges[i + 1]
bar_len = int(40 * count / max_count) if max_count > 0 else 0
bar = "#" * bar_len
# Choose appropriate unit
if right < 1000:
label = f"{left:7.1f}-{right:7.1f}us"
elif right < 1000000:
label = f"{left/1000:7.2f}-{right/1000:7.2f}ms"
else:
label = f"{left/1e6:7.3f}-{right/1e6:7.3f}s"
print(f"{label} |{bar} ({count})")
def export_csv(data: dict, output_path: Path):
"""Export transitions to CSV file."""
times = data['times']
initial = data['initial_state']
with open(output_path, 'w') as f:
f.write("index,time_s,state,duration_us\n")
for i, t in enumerate(times):
state = (initial + i) % 2
if i < len(times) - 1:
dur = (times[i + 1] - t) * 1e6
else:
dur = 0
f.write(f"{i},{t:.9f},{state},{dur:.3f}\n")
print(f"Exported {len(times)} transitions to {output_path}")
def main():
parser = argparse.ArgumentParser(
description="Analyze digital signal captures from Saleae Logic MSO"
)
parser.add_argument("file", type=Path, help="Binary capture file (.bin)")
parser.add_argument("--histogram", action="store_true",
help="Show timing histogram")
parser.add_argument("--bins", type=int, default=20,
help="Number of histogram bins (default: 20)")
parser.add_argument("--export", type=Path, metavar="CSV",
help="Export transitions to CSV file")
parser.add_argument("--clusters", action="store_true",
help="Show detected timing clusters")
parser.add_argument("--raw", action="store_true",
help="Show raw duration values")
parser.add_argument("-n", type=int, default=20,
help="Number of raw values to show (default: 20)")
args = parser.parse_args()
if not args.file.exists():
print(f"Error: File not found: {args.file}")
sys.exit(1)
try:
data = load_capture(args.file)
except Exception as e:
print(f"Error loading file: {e}")
sys.exit(1)
analysis = analyze_timing(data)
if 'error' in analysis:
print(f"Error: {analysis['error']}")
sys.exit(1)
# Print basic info
print(f"File: {args.file}")
print(f"Sample rate: {data['sample_rate']/1e6:.1f} MHz")
print(f"Capture duration: {analysis['capture_duration_s']:.3f}s")
print(f"Signal duration: {analysis['signal_duration_s']:.3f}s")
print(f"Initial state: {analysis['initial_state']}")
print(f"Total transitions: {analysis['total_transitions']}")
print()
# Timing summary
print("Timing Summary")
print("-" * 40)
a = analysis['all']
print(f"All durations: min={a['min_us']:.1f}us max={a['max_us']:.1f}us mean={a['mean_us']:.1f}us")
h = analysis['high']
print(f"HIGH pulses ({h['count']}): min={h['min_us']:.1f}us max={h['max_us']:.1f}us mean={h['mean_us']:.1f}us")
l = analysis['low']
print(f"LOW gaps ({l['count']}): min={l['min_us']:.1f}us max={l['max_us']:.1f}us mean={l['mean_us']:.1f}us")
print()
# Protocol guesses
guesses = guess_protocol(analysis)
if guesses:
print("Protocol Guesses")
print("-" * 40)
for name, confidence, details in guesses:
print(f" {name} ({confidence*100:.0f}% confidence)")
print(f" {details}")
print()
# Clusters
if args.clusters:
print("Detected Timing Clusters")
print("-" * 40)
high_clusters = detect_clusters(analysis['high_durations_us'])
print("HIGH pulse clusters:")
for center, count in high_clusters[:5]:
if center < 1000:
print(f" ~{center:.1f}us ({count} occurrences)")
else:
print(f" ~{center/1000:.2f}ms ({count} occurrences)")
low_clusters = detect_clusters(analysis['low_durations_us'])
print("LOW gap clusters:")
for center, count in low_clusters[:5]:
if center < 1000:
print(f" ~{center:.1f}us ({count} occurrences)")
else:
print(f" ~{center/1000:.2f}ms ({count} occurrences)")
print()
# Raw values
if args.raw:
print(f"First {args.n} Transitions")
print("-" * 40)
durations = analysis['durations_us']
initial = 0 if analysis['initial_state'] == 'LOW' else 1
for i in range(min(args.n, len(durations))):
state = "HIGH" if (i + initial) % 2 == 0 else "LOW"
dur = durations[i]
if dur < 1000:
print(f" [{i:3d}] {state}: {dur:.1f}us")
else:
print(f" [{i:3d}] {state}: {dur/1000:.2f}ms")
print()
# Histogram
if args.histogram:
print_histogram(analysis['durations_us'], bins=args.bins, title="All Durations")
print_histogram(analysis['high_durations_us'], bins=args.bins, title="HIGH Pulse Durations")
print_histogram(analysis['low_durations_us'], bins=args.bins, title="LOW Gap Durations")
# Export
if args.export:
export_csv(data, args.export)
if __name__ == "__main__":
main()
Logic MSO Analysis Examples
Example 1: Unknown Protocol Analysis
Scenario: You captured an unknown digital signal and need to identify the protocol.
Step 1: Get an overview
python3 skills/logicmso/analyze_protocol.py capture.binStep 2: Look at timing distribution
python3 skills/logicmso/analyze_protocol.py capture.bin --histogram --clustersStep 3: Examine raw transitions
python3 skills/logicmso/analyze_protocol.py capture.bin --raw -n 50Step 4: Export for external analysis
python3 skills/logicmso/analyze_protocol.py capture.bin --export transitions.csv---
Example 2: UART Signal Analysis
Scenario: You suspect the signal is UART but need to determine the baud rate.
Identifying UART
Look for these characteristics:
- Signal idles HIGH
- Consistent bit periods
- Durations are multiples of the bit period
python3 skills/logicmso/analyze_protocol.py uart_capture.bin --clustersIf the analyzer suggests UART with a specific baud rate:
Protocol Guesses
----------------------------------------
UART (115200 baud) (85% confidence)
Bit period ~8.7usManual UART Decoding (Python)
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("uart_capture.bin")
chunk = f.contents.chunks[0]
times = np.array(chunk.transition_times)
BAUD = 115200
BIT_PERIOD = 1 / BAUD
def decode_uart_byte(start_time, times, bit_period):
"""Decode a single UART byte starting at start_time."""
byte_val = 0
for bit_num in range(8):
sample_time = start_time + (1.5 + bit_num) * bit_period
idx = np.searchsorted(times, sample_time)
state = (chunk.initial_state + idx) % 2
if state:
byte_val |= (1 << bit_num)
return byte_val
# Decode bytes from transitions...---
Example 3: SPI Analysis
Scenario: Multi-channel SPI capture with clock and data lines.
Identifying SPI signals
- Look for a regular clock signal (SCLK)
- CS (chip select) goes LOW during transaction
- Data sampled on clock edges
from saleae.mso_api.binary_files import read_file
import numpy as np
# Load clock and data channels
clk_file = read_file("spi_clk.bin")
mosi_file = read_file("spi_mosi.bin")
clk_chunk = clk_file.contents.chunks[0]
mosi_chunk = mosi_file.contents.chunks[0]
clk_times = np.array(clk_chunk.transition_times)
mosi_times = np.array(mosi_chunk.transition_times)
# Find rising edges of clock (sample points for CPHA=0)
rising_edges = clk_times[0::2] if clk_chunk.initial_state == 0 else clk_times[1::2]
# Sample MOSI at each rising edge
def get_state_at_time(times, initial_state, t):
idx = np.searchsorted(times, t)
return (initial_state + idx) % 2
bits = [get_state_at_time(mosi_times, mosi_chunk.initial_state, t) for t in rising_edges]
# Group into bytes
bytes_out = []
for i in range(0, len(bits) - 7, 8):
byte_val = sum(bits[i+j] << (7-j) for j in range(8)) # MSB first
bytes_out.append(byte_val)
print("SPI data:", bytes(bytes_out))---
Example 4: I2C Analysis
Scenario: Captured I2C SDA and SCL lines.
Identifying I2C
- Both lines idle HIGH
- START: SDA falls while SCL is HIGH
- STOP: SDA rises while SCL is HIGH
- 9 clock pulses per byte (8 data + ACK)
from saleae.mso_api.binary_files import read_file
import numpy as np
sda_file = read_file("i2c_sda.bin")
scl_file = read_file("i2c_scl.bin")
sda_chunk = sda_file.contents.chunks[0]
scl_chunk = scl_file.contents.chunks[0]
sda_times = np.array(sda_chunk.transition_times)
scl_times = np.array(scl_chunk.transition_times)
def get_state_at_time(times, initial_state, t):
idx = np.searchsorted(times, t)
return (initial_state + idx) % 2
# Find START conditions (SDA falls while SCL is HIGH)
starts = []
for i, t in enumerate(sda_times):
if i % 2 == (0 if sda_chunk.initial_state == 1 else 1): # Falling edge
if get_state_at_time(scl_times, scl_chunk.initial_state, t) == 1:
starts.append(t)
print(f"Found {len(starts)} I2C START conditions")---
Example 5: 1-Wire Analysis
Scenario: Single-wire protocol (e.g., DS18B20 temperature sensor).
Identifying 1-Wire
- Single signal, idles HIGH
- Reset pulse: ~480us LOW
- Presence pulse: ~60-240us LOW response
- Data: short LOW pulses (1-15us = 1, 60-120us = 0)
from saleae.mso_api.binary_files import read_file
import numpy as np
f = read_file("onewire_capture.bin")
chunk = f.contents.chunks[0]
times = np.array(chunk.transition_times)
durations_us = np.diff(times) * 1e6
# Find reset pulses (long LOW periods)
low_idx = 1 if chunk.initial_state == 1 else 0
low_durations = durations_us[low_idx::2]
reset_pulses = [(i, d) for i, d in enumerate(low_durations) if d > 400]
print(f"Found {len(reset_pulses)} reset pulses")
# Decode data bits (after reset)
# Short LOW = 1, Long LOW = 0
data_bits = []
for d in low_durations:
if 1 < d < 20:
data_bits.append(1)
elif 50 < d < 130:
data_bits.append(0)---
Python API Quick Reference
Load and inspect a capture
from saleae.mso_api.binary_files import read_file
from pathlib import Path
import numpy as np
# Load file
f = read_file(Path("capture.bin"))
chunk = f.contents.chunks[0]
# Basic info
print(f"Sample rate: {chunk.sample_rate}")
print(f"Initial state: {chunk.initial_state}")
print(f"Transitions: {len(chunk.transition_times)}")
# Get durations
times = np.array(chunk.transition_times)
durations_us = np.diff(times) * 1e6
# Separate HIGH and LOW durations
if chunk.initial_state == 0: # Starts LOW
high_durations = durations_us[0::2] # Even indices
low_durations = durations_us[1::2] # Odd indices
else: # Starts HIGH
high_durations = durations_us[1::2] # Odd indices
low_durations = durations_us[0::2] # Even indicesFind unique timing values
# Round to nearest 10us and find unique values
unique_high = sorted(set(round(d, -1) for d in high_durations))
unique_low = sorted(set(round(d, -1) for d in low_durations))
print(f"HIGH pulse values: {unique_high}")
print(f"LOW gap values: {unique_low}")Get signal state at a specific time
def get_state_at_time(times, initial_state, t):
"""Return signal state (0 or 1) at time t."""
idx = np.searchsorted(times, t)
return (initial_state + idx) % 2