Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/pytest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,22 @@ jobs:
sudo apt-get update
sudo apt-get install -y libgpiod-dev liblgpio-dev

- name: Install sigrok-cli (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y sigrok-cli

- name: Install Qemu (macOS)
if: runner.os == 'macOS'
run: |
brew install qemu

- name: Install sigrok-cli (macOS)
if: runner.os == 'macOS'
run: |
brew install sigrok-cli

- name: Cache Fedora Cloud images
id: cache-fedora-cloud-images
uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN dnf install -y make git && \
COPY --from=uv /uv /uvx /bin/

FROM fedora:42 AS product
RUN dnf install -y python3 ustreamer libusb1 android-tools python3-libgpiod && \
RUN dnf install -y python3 ustreamer libusb1 android-tools python3-libgpiod sigrok-cli && \
dnf clean all && \
rm -rf /var/cache/dnf
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
Expand Down
3 changes: 3 additions & 0 deletions docs/source/reference/package-apis/drivers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Drivers for debugging and programming devices:
* **[QEMU](qemu.md)** (`jumpstarter-driver-qemu`) - QEMU virtualization platform
* **[Corellium](corellium.md)** (`jumpstarter-driver-corellium`) - Corellium
virtualization platform
* **[Sigrok](sigrok.md)** (`jumpstarter-driver-sigrok`) - Logic analyzer and
oscilloscope support via sigrok-cli
* **[U-Boot](uboot.md)** (`jumpstarter-driver-uboot`) - Universal Bootloader
interface
* **[RideSX](ridesx.md)** (`jumpstarter-driver-ridesx`) - Flashing and power management for Qualcomm RideSX devices
Expand Down Expand Up @@ -104,6 +106,7 @@ gpiod.md
ridesx.md
sdwire.md
shell.md
sigrok.md
ssh.md
snmp.md
tasmota.md
Expand Down
1 change: 1 addition & 0 deletions docs/source/reference/package-apis/drivers/sigrok.md
3 changes: 3 additions & 0 deletions packages/jumpstarter-driver-sigrok/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__pycache__/
.coverage
coverage.xml
227 changes: 227 additions & 0 deletions packages/jumpstarter-driver-sigrok/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
# Sigrok Driver

`jumpstarter-driver-sigrok` wraps [sigrok-cli](https://sigrok.org/wiki/Sigrok-cli) to provide logic analyzer and oscilloscope capture from Jumpstarter exporters. It supports:
- **Logic analyzers** (digital channels)
- **Oscilloscopes** (analog channels) - voltage waveform capture
- One-shot and streaming capture
- Multiple output formats with parsing (VCD, CSV, Bits, ASCII)

## Installation

```shell
pip3 install --extra-index-url https://pkg.jumpstarter.dev/simple/ jumpstarter-driver-sigrok
```

## Configuration (exporter)

```yaml
export:
sigrok:
type: jumpstarter_driver_sigrok.driver.Sigrok
driver: fx2lafw # sigrok driver (demo, fx2lafw, rigol-ds, etc.)
conn: auto # optional: USB VID.PID, serial path, or "auto" for auto-detect
channels: # optional: map device channels to friendly names
D0: clk
D1: mosi
D2: miso
D3: cs
```

### Configuration Parameters

| Parameter | Description | Type | Required | Default |
|-----------|-------------|------|----------|---------|
| `driver` | Sigrok driver name (e.g., `demo`, `fx2lafw`, `rigol-ds`) | str | yes | - |
| `conn` | Connection string (USB VID.PID, serial path, or `"auto"` for auto-detect) | str \| None | no | "auto" |
| `executable` | Path to `sigrok-cli` executable | str | no | Auto-detected from PATH |
| `channels` | Channel mapping from device names (D0, A0) to semantic names (clk, voltage) | dict[str, str] | no | {} (empty) |

## CaptureConfig Parameters (client-side)

| Parameter | Description | Type | Required | Default |
|-----------|-------------|------|----------|---------|
| `sample_rate` | Sampling rate (e.g., `"1M"`, `"8MHz"`, `"24000000"`) | str | no | "1M" |
| `samples` | Number of samples to capture (`None` for continuous) | int \| None | no | None |
| `pretrigger` | Number of samples to capture before trigger | int \| None | no | None |
| `triggers` | Trigger conditions by channel name (e.g., `{"cs": "falling"}`) | dict[str, str] \| None | no | None |
| `channels` | List of channel names to capture (overrides defaults) | list[str] \| None | no | None |
| `output_format` | Output format (vcd, csv, bits, ascii, srzip, binary) | str | no | "vcd" |

## Client API

- `scan()` — list devices for the configured driver
- `capture(config)` — one-shot capture, returns `CaptureResult` with base64 data
- `capture_stream(config)` — streaming capture via `--continuous`
- `get_driver_info()` — driver, conn, channel map
- `get_channel_map()` — device-to-semantic name mappings
- `list_output_formats()` — supported formats (csv, srzip, vcd, binary, bits, ascii)

## Output Formats

The driver supports multiple output formats. **VCD (Value Change Dump) is the default** because:
- ✅ **Efficient**: Only records signal changes (not every sample)
- ✅ **Precise timing**: Includes exact timestamps in nanoseconds
- ✅ **Widely supported**: Standard format for signal analysis tools
- ✅ **Mixed signals**: Handles both digital and analog data

### Available Formats

| Format | Use Case | Decoded By |
|--------|----------|------------|
| `vcd` (default) | Change-based signals with timing | `result.decode()` → `list[Sample]` |
| `csv` | All samples with timing | `result.decode()` → `list[Sample]` |
| `bits` | Bit sequences by channel | `result.decode()` → `dict[str, list[int]]` |
| `ascii` | ASCII art visualization | `result.decode()` → `str` |
| `srzip` | Raw sigrok session (for PulseView) | `result.data` (raw bytes) |
| `binary` | Raw binary data | `result.data` (raw bytes) |

### Output Format Constants

```python
from jumpstarter_driver_sigrok.common import OutputFormat

config = CaptureConfig(
sample_rate="1MHz",
samples=1000,
output_format=OutputFormat.VCD, # or CSV, BITS, ASCII, SRZIP, BINARY
)
```

## Examples

### Example 1: Simple Capture (VCD format - default)

**Python client code:**
```python
from jumpstarter_driver_sigrok.common import CaptureConfig

# Capture with default VCD format (efficient, change-based with timing)
config = CaptureConfig(
sample_rate="1MHz",
samples=1000,
channels=["D0", "D1", "D2"], # Use device channel names or mapped names
)
result = client.capture(config)

# Decode VCD to get samples with timing
samples = result.decode() # list[Sample]
for sample in samples[:5]:
print(f"Time: {sample.time}s, Values: {sample.values}")
```

**Equivalent sigrok-cli command:**
```bash
sigrok-cli -d fx2lafw -C D0,D1,D2 \
-c samplerate=1MHz --samples 1000 \
-O vcd -o /tmp/capture.vcd
```

---

### Example 2: Triggered Capture with Pretrigger

**Python client code:**
```python
from jumpstarter_driver_sigrok.common import CaptureConfig

# Capture with trigger and pretrigger buffer (VCD format - default)
config = CaptureConfig(
sample_rate="8MHz",
samples=20000,
pretrigger=5000, # Capture 5000 samples before trigger
triggers={"D0": "rising"}, # Trigger on D0 rising edge
channels=["D0", "D1", "D2", "D3"],
# output_format defaults to VCD (efficient change-based format)
)
result = client.capture(config)

# Decode to analyze signal changes with precise timing
samples = result.decode() # list[Sample] - only changes recorded
print(f"Captured {len(samples)} signal changes")

# Access timing and values
for sample in samples[:3]:
print(f"Time: {sample.time}s, Changed: {sample.values}")
```

**Equivalent sigrok-cli command:**
```bash
sigrok-cli -d fx2lafw -C D0,D1,D2,D3 \
-c samplerate=8MHz,samples=20000,pretrigger=5000 \
--triggers D0=rising \
-O vcd -o /tmp/capture.vcd
```

---

### Example 3: Oscilloscope (Analog Channels)

**Exporter configuration:**
```yaml
export:
oscilloscope:
type: jumpstarter_driver_sigrok.driver.Sigrok
driver: rigol-ds # or demo for testing
conn: usb # or serial path
channels:
A0: CH1
A1: CH2
```

**Python client code:**
```python
from jumpstarter_driver_sigrok.common import CaptureConfig, OutputFormat

# Capture analog waveforms
config = CaptureConfig(
sample_rate="1MHz",
samples=10000,
channels=["CH1", "CH2"], # Analog channels
output_format=OutputFormat.CSV, # CSV for voltage values
)
result = client.capture(config)

# Parse voltage data
samples = result.decode() # list[Sample]
for sample in samples[:5]:
print(f"Time: {sample.time}s")
print(f" CH1: {sample.values.get('A0', 'N/A')}V")
print(f" CH2: {sample.values.get('A1', 'N/A')}V")
```

**Equivalent sigrok-cli command:**
```bash
sigrok-cli -d rigol-ds:conn=usb -C A0=CH1,A1=CH2 \
-c samplerate=1MHz --samples 10000 \
-O csv -o /tmp/capture.csv
```

---

### Example 4: Bits Format (Simple Bit Sequences)

**Python client code:**
```python
from jumpstarter_driver_sigrok.common import CaptureConfig, OutputFormat

# Capture in bits format (useful for visual inspection)
config = CaptureConfig(
sample_rate="100kHz",
samples=100,
channels=["D0", "D1", "D2"],
output_format=OutputFormat.BITS,
)
result = client.capture(config)

# Get bit sequences per channel
bits_by_channel = result.decode() # dict[str, list[int]]
for channel, bits in bits_by_channel.items():
print(f"{channel}: {''.join(map(str, bits[:20]))}") # First 20 bits
```

**Equivalent sigrok-cli command:**
```bash
sigrok-cli -d demo -C D0,D1,D2 \
-c samplerate=100kHz --samples 100 \
-O bits -o /tmp/capture.bits
```
22 changes: 22 additions & 0 deletions packages/jumpstarter-driver-sigrok/examples/exporter.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
metadata:
namespace: default
name: demo
endpoint: grpc.jumpstarter.192.168.0.203.nip.io:8082
token: "<token>"
export:
sigrok:
type: jumpstarter_driver_sigrok.driver.Sigrok
config:
driver: demo
conn: auto
channels:
D0: vcc
D1: cs
D2: miso
D3: mosi
D4: clk
D5: sda
D6: scl

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from jumpstarter_driver_sigrok.common import (
CaptureConfig,
CaptureResult,
DecoderConfig,
OutputFormat,
Sample,
)
from jumpstarter_driver_sigrok.driver import Sigrok

__all__ = ["Sigrok", "CaptureConfig", "CaptureResult", "DecoderConfig", "OutputFormat", "Sample"]

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from dataclasses import dataclass

from .common import CaptureConfig, CaptureResult
from jumpstarter.client import DriverClient


@dataclass(kw_only=True)
class SigrokClient(DriverClient):
"""Client methods for the Sigrok driver."""

def scan(self) -> str:
return self.call("scan")

def capture(self, config: CaptureConfig | dict) -> CaptureResult:
return CaptureResult.model_validate(self.call("capture", config))

def capture_stream(self, config: CaptureConfig | dict):
"""Stream capture data from sigrok-cli.

Args:
config: CaptureConfig or dict with capture parameters

Yields:
bytes: Chunks of captured data
"""
for chunk in self.streamingcall("capture_stream", config):
yield chunk

def get_driver_info(self) -> dict:
return self.call("get_driver_info")

def get_channel_map(self) -> dict:
return self.call("get_channel_map")

def list_output_formats(self) -> list[str]:
return self.call("list_output_formats")

Loading