oscura 0.8.0__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +19 -19
- oscura/__main__.py +4 -0
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/ml/signal_classifier.py +6 -0
- oscura/analyzers/patterns/__init__.py +66 -0
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/basic.py +10 -7
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/measurements.py +200 -156
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +182 -84
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/flexray/fibex.py +9 -1
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +2 -8
- oscura/core/schemas/packet_format.json +4 -24
- oscura/core/schemas/protocol_definition.json +2 -12
- oscura/core/types.py +232 -239
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/validation.py +17 -10
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/formatting/measurements.py +55 -14
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/sessions/legacy.py +49 -1
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +46 -284
- oscura/visualization/batch.py +72 -433
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +12 -9
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +11 -6
- oscura-0.11.0.dist-info/METADATA +460 -0
- {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/RECORD +120 -145
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- oscura-0.8.0.dist-info/METADATA +0 -661
- {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/WHEEL +0 -0
- {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1246 +0,0 @@
|
|
|
1
|
-
"""Protocol decoder visualization functions.
|
|
2
|
-
|
|
3
|
-
This module provides visualization functions for decoded protocol packets,
|
|
4
|
-
creating timing diagrams with multi-level annotations for protocol analysis.
|
|
5
|
-
|
|
6
|
-
Example:
|
|
7
|
-
>>> from oscura.analyzers.protocols.uart import UARTDecoder
|
|
8
|
-
>>> from oscura.visualization.protocols import plot_protocol_decode
|
|
9
|
-
>>>
|
|
10
|
-
>>> decoder = UARTDecoder(baudrate=115200)
|
|
11
|
-
>>> packets = list(decoder.decode(trace))
|
|
12
|
-
>>> fig = plot_protocol_decode(packets, trace=trace, title="UART Decode")
|
|
13
|
-
|
|
14
|
-
References:
|
|
15
|
-
- Protocol visualization best practices
|
|
16
|
-
- Wavedrom-style timing diagrams
|
|
17
|
-
- sigrok annotation system
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from __future__ import annotations
|
|
21
|
-
|
|
22
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
23
|
-
|
|
24
|
-
import numpy as np
|
|
25
|
-
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
from matplotlib.axes import Axes
|
|
28
|
-
from matplotlib.figure import Figure
|
|
29
|
-
from numpy.typing import NDArray
|
|
30
|
-
|
|
31
|
-
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
import matplotlib.pyplot as plt
|
|
35
|
-
from matplotlib import patches
|
|
36
|
-
|
|
37
|
-
HAS_MATPLOTLIB = True
|
|
38
|
-
except ImportError:
|
|
39
|
-
HAS_MATPLOTLIB = False
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def plot_protocol_decode(
|
|
43
|
-
packets: list[ProtocolPacket],
|
|
44
|
-
*,
|
|
45
|
-
trace: DigitalTrace | None = None,
|
|
46
|
-
trace_channel: str | None = None,
|
|
47
|
-
annotation_levels: list[str] | Literal["all"] = "all",
|
|
48
|
-
time_range: tuple[float, float] | None = None,
|
|
49
|
-
time_unit: str = "auto",
|
|
50
|
-
show_data: bool = True,
|
|
51
|
-
show_errors: bool = True,
|
|
52
|
-
colorize: bool = True,
|
|
53
|
-
figsize: tuple[float, float] | None = None,
|
|
54
|
-
title: str | None = None,
|
|
55
|
-
) -> Figure:
|
|
56
|
-
"""Plot decoded protocol packets with multi-level annotations.
|
|
57
|
-
|
|
58
|
-
Creates a timing diagram showing the original waveform (if provided)
|
|
59
|
-
and annotation rows for decoded protocol data at different levels
|
|
60
|
-
(bits, bytes, fields, packets, messages).
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
packets: List of decoded protocol packets to visualize.
|
|
64
|
-
trace: Optional digital trace to plot alongside annotations.
|
|
65
|
-
trace_channel: Name of trace channel (default: protocol name).
|
|
66
|
-
annotation_levels: Which annotation levels to display ("all" or list of level names).
|
|
67
|
-
time_range: Time range to plot (t_min, t_max) in seconds. None = auto from packets.
|
|
68
|
-
time_unit: Time unit for x-axis ("s", "ms", "us", "ns", "auto").
|
|
69
|
-
show_data: Show decoded data values in annotations.
|
|
70
|
-
show_errors: Highlight packets with errors.
|
|
71
|
-
colorize: Use color coding for different packet types.
|
|
72
|
-
figsize: Figure size (width, height). Auto-calculated if None.
|
|
73
|
-
title: Plot title.
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
Matplotlib Figure object.
|
|
77
|
-
|
|
78
|
-
Raises:
|
|
79
|
-
ImportError: If matplotlib is not available.
|
|
80
|
-
ValueError: If packets list is empty.
|
|
81
|
-
|
|
82
|
-
Example:
|
|
83
|
-
>>> decoder = UARTDecoder(baudrate=9600)
|
|
84
|
-
>>> packets = list(decoder.decode(rx_trace))
|
|
85
|
-
>>> fig = plot_protocol_decode(
|
|
86
|
-
... packets,
|
|
87
|
-
... trace=rx_trace,
|
|
88
|
-
... time_unit="ms",
|
|
89
|
-
... title="UART Communication"
|
|
90
|
-
... )
|
|
91
|
-
|
|
92
|
-
References:
|
|
93
|
-
VIS-030: Protocol Decode Visualization
|
|
94
|
-
"""
|
|
95
|
-
_validate_plot_inputs(packets)
|
|
96
|
-
protocol = packets[0].protocol
|
|
97
|
-
t_min, t_max, time_mult, time_unit = _calculate_time_parameters(packets, time_range, time_unit)
|
|
98
|
-
fig, axes = _create_figure_layout(trace, figsize)
|
|
99
|
-
ax_idx = _plot_waveform_if_present(
|
|
100
|
-
axes, trace, trace_channel, protocol, t_min, t_max, time_mult
|
|
101
|
-
)
|
|
102
|
-
_plot_packet_timeline(
|
|
103
|
-
axes[ax_idx], packets, protocol, t_min, t_max, time_mult, show_data, show_errors, colorize
|
|
104
|
-
)
|
|
105
|
-
_finalize_plot_layout(axes, t_min, t_max, time_mult, time_unit, title)
|
|
106
|
-
|
|
107
|
-
return fig
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _validate_plot_inputs(packets: list[ProtocolPacket]) -> None:
|
|
111
|
-
"""Validate plot inputs.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
packets: List of protocol packets.
|
|
115
|
-
|
|
116
|
-
Raises:
|
|
117
|
-
ImportError: If matplotlib not available.
|
|
118
|
-
ValueError: If packets list is empty.
|
|
119
|
-
"""
|
|
120
|
-
if not HAS_MATPLOTLIB:
|
|
121
|
-
raise ImportError("matplotlib is required for visualization")
|
|
122
|
-
if len(packets) == 0:
|
|
123
|
-
raise ValueError("packets list cannot be empty")
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _calculate_time_parameters(
|
|
127
|
-
packets: list[ProtocolPacket],
|
|
128
|
-
time_range: tuple[float, float] | None,
|
|
129
|
-
time_unit: str,
|
|
130
|
-
) -> tuple[float, float, float, str]:
|
|
131
|
-
"""Calculate time range and multiplier for plotting.
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
packets: List of packets for auto time range.
|
|
135
|
-
time_range: User-specified time range or None.
|
|
136
|
-
time_unit: Time unit string.
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
Tuple of (t_min, t_max, time_mult, time_unit).
|
|
140
|
-
"""
|
|
141
|
-
if time_range is None:
|
|
142
|
-
t_min = min(p.timestamp for p in packets)
|
|
143
|
-
t_max = max(p.end_timestamp if p.end_timestamp else p.timestamp for p in packets)
|
|
144
|
-
padding = (t_max - t_min) * 0.1
|
|
145
|
-
t_min -= padding
|
|
146
|
-
t_max += padding
|
|
147
|
-
else:
|
|
148
|
-
t_min, t_max = time_range
|
|
149
|
-
|
|
150
|
-
if time_unit == "auto":
|
|
151
|
-
time_range_val = t_max - t_min
|
|
152
|
-
if time_range_val < 1e-6:
|
|
153
|
-
time_unit = "ns"
|
|
154
|
-
time_mult = 1e9
|
|
155
|
-
elif time_range_val < 1e-3:
|
|
156
|
-
time_unit = "us"
|
|
157
|
-
time_mult = 1e6
|
|
158
|
-
elif time_range_val < 1:
|
|
159
|
-
time_unit = "ms"
|
|
160
|
-
time_mult = 1e3
|
|
161
|
-
else:
|
|
162
|
-
time_unit = "s"
|
|
163
|
-
time_mult = 1.0
|
|
164
|
-
else:
|
|
165
|
-
time_mult = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
166
|
-
|
|
167
|
-
return t_min, t_max, time_mult, time_unit
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _create_figure_layout(
|
|
171
|
-
trace: DigitalTrace | None, figsize: tuple[float, float] | None
|
|
172
|
-
) -> tuple[Figure, list[Axes]]:
|
|
173
|
-
"""Create figure and axes layout.
|
|
174
|
-
|
|
175
|
-
Args:
|
|
176
|
-
trace: Optional trace for determining row count.
|
|
177
|
-
figsize: Figure size or None for auto.
|
|
178
|
-
|
|
179
|
-
Returns:
|
|
180
|
-
Tuple of (figure, axes_list).
|
|
181
|
-
"""
|
|
182
|
-
n_rows = 1 if trace is None else 2
|
|
183
|
-
|
|
184
|
-
if figsize is None:
|
|
185
|
-
width = 14
|
|
186
|
-
height = max(4, n_rows * 1.5 + 1)
|
|
187
|
-
figsize = (width, height)
|
|
188
|
-
|
|
189
|
-
fig, axes = plt.subplots(
|
|
190
|
-
n_rows,
|
|
191
|
-
1,
|
|
192
|
-
figsize=figsize,
|
|
193
|
-
sharex=True,
|
|
194
|
-
gridspec_kw={"hspace": 0.15, "height_ratios": [1] * n_rows},
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
if n_rows == 1:
|
|
198
|
-
axes = [axes]
|
|
199
|
-
|
|
200
|
-
return fig, axes
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _plot_waveform_if_present(
|
|
204
|
-
axes: list[Axes],
|
|
205
|
-
trace: DigitalTrace | None,
|
|
206
|
-
trace_channel: str | None,
|
|
207
|
-
protocol: str,
|
|
208
|
-
t_min: float,
|
|
209
|
-
t_max: float,
|
|
210
|
-
time_mult: float,
|
|
211
|
-
) -> int:
|
|
212
|
-
"""Plot waveform trace if provided.
|
|
213
|
-
|
|
214
|
-
Args:
|
|
215
|
-
axes: List of axes to plot on.
|
|
216
|
-
trace: Optional digital trace.
|
|
217
|
-
trace_channel: Channel name override.
|
|
218
|
-
protocol: Protocol name for default label.
|
|
219
|
-
t_min: Minimum time value.
|
|
220
|
-
t_max: Maximum time value.
|
|
221
|
-
time_mult: Time unit multiplier.
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
Index of next available axis.
|
|
225
|
-
"""
|
|
226
|
-
if trace is None:
|
|
227
|
-
return 0
|
|
228
|
-
|
|
229
|
-
ax = axes[0]
|
|
230
|
-
trace_time = trace.time_vector * time_mult
|
|
231
|
-
trace_data = trace.data.astype(float)
|
|
232
|
-
|
|
233
|
-
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
234
|
-
trace_time = trace_time[mask]
|
|
235
|
-
trace_data = trace_data[mask]
|
|
236
|
-
|
|
237
|
-
_plot_digital_waveform(ax, trace_time, trace_data)
|
|
238
|
-
|
|
239
|
-
channel_name = trace_channel if trace_channel else protocol
|
|
240
|
-
ax.set_ylabel(channel_name, rotation=0, ha="right", va="center", fontsize=10)
|
|
241
|
-
ax.set_ylim(-0.2, 1.3)
|
|
242
|
-
ax.set_yticks([])
|
|
243
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
244
|
-
|
|
245
|
-
return 1
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def _plot_packet_timeline(
|
|
249
|
-
ax: Axes,
|
|
250
|
-
packets: list[ProtocolPacket],
|
|
251
|
-
protocol: str,
|
|
252
|
-
t_min: float,
|
|
253
|
-
t_max: float,
|
|
254
|
-
time_mult: float,
|
|
255
|
-
show_data: bool,
|
|
256
|
-
show_errors: bool,
|
|
257
|
-
colorize: bool,
|
|
258
|
-
) -> None:
|
|
259
|
-
"""Plot packet timeline on axis.
|
|
260
|
-
|
|
261
|
-
Args:
|
|
262
|
-
ax: Matplotlib axis.
|
|
263
|
-
packets: List of packets to plot.
|
|
264
|
-
protocol: Protocol name.
|
|
265
|
-
t_min: Minimum time value.
|
|
266
|
-
t_max: Maximum time value.
|
|
267
|
-
time_mult: Time unit multiplier.
|
|
268
|
-
show_data: Show data annotations.
|
|
269
|
-
show_errors: Highlight errors.
|
|
270
|
-
colorize: Use color coding.
|
|
271
|
-
"""
|
|
272
|
-
for packet in packets:
|
|
273
|
-
if packet.timestamp < t_min or packet.timestamp > t_max:
|
|
274
|
-
continue
|
|
275
|
-
|
|
276
|
-
start = packet.timestamp * time_mult
|
|
277
|
-
end = (
|
|
278
|
-
packet.end_timestamp if packet.end_timestamp else packet.timestamp + 0.001
|
|
279
|
-
) * time_mult
|
|
280
|
-
|
|
281
|
-
color = _determine_packet_color(packet, protocol, show_errors, colorize)
|
|
282
|
-
|
|
283
|
-
rect = patches.Rectangle(
|
|
284
|
-
(start, 0.1),
|
|
285
|
-
end - start,
|
|
286
|
-
0.8,
|
|
287
|
-
facecolor=color,
|
|
288
|
-
edgecolor="black",
|
|
289
|
-
linewidth=0.8,
|
|
290
|
-
alpha=0.7,
|
|
291
|
-
)
|
|
292
|
-
ax.add_patch(rect)
|
|
293
|
-
|
|
294
|
-
if show_data and packet.data:
|
|
295
|
-
_add_packet_annotation(ax, packet, start, end, show_errors)
|
|
296
|
-
|
|
297
|
-
if show_errors and packet.errors:
|
|
298
|
-
ax.plot(start, 1.1, "rx", markersize=8, markeredgewidth=2)
|
|
299
|
-
|
|
300
|
-
ax.set_ylabel(f"{protocol}\nPackets", rotation=0, ha="right", va="center", fontsize=10)
|
|
301
|
-
ax.set_ylim(0, 1.2)
|
|
302
|
-
ax.set_yticks([])
|
|
303
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def _determine_packet_color(
|
|
307
|
-
packet: ProtocolPacket, protocol: str, show_errors: bool, colorize: bool
|
|
308
|
-
) -> str:
|
|
309
|
-
"""Determine packet rectangle color.
|
|
310
|
-
|
|
311
|
-
Args:
|
|
312
|
-
packet: Protocol packet.
|
|
313
|
-
protocol: Protocol name.
|
|
314
|
-
show_errors: Whether to highlight errors.
|
|
315
|
-
colorize: Whether to use protocol colors.
|
|
316
|
-
|
|
317
|
-
Returns:
|
|
318
|
-
Color string.
|
|
319
|
-
"""
|
|
320
|
-
if show_errors and packet.errors:
|
|
321
|
-
return "#ff6b6b"
|
|
322
|
-
elif colorize:
|
|
323
|
-
return _get_packet_color(packet, protocol)
|
|
324
|
-
else:
|
|
325
|
-
return "#4ecdc4"
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def _add_packet_annotation(
|
|
329
|
-
ax: Axes, packet: ProtocolPacket, start: float, end: float, show_errors: bool
|
|
330
|
-
) -> None:
|
|
331
|
-
"""Add data annotation to packet.
|
|
332
|
-
|
|
333
|
-
Args:
|
|
334
|
-
ax: Matplotlib axis.
|
|
335
|
-
packet: Protocol packet.
|
|
336
|
-
start: Start time in scaled units.
|
|
337
|
-
end: End time in scaled units.
|
|
338
|
-
show_errors: Whether errors are highlighted.
|
|
339
|
-
"""
|
|
340
|
-
data_str = _format_packet_data(packet)
|
|
341
|
-
mid_time = (start + end) / 2
|
|
342
|
-
text_color = "white" if not (show_errors and packet.errors) else "black"
|
|
343
|
-
ax.text(
|
|
344
|
-
mid_time,
|
|
345
|
-
0.5,
|
|
346
|
-
data_str,
|
|
347
|
-
ha="center",
|
|
348
|
-
va="center",
|
|
349
|
-
fontsize=8,
|
|
350
|
-
fontweight="bold",
|
|
351
|
-
color=text_color,
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _finalize_plot_layout(
|
|
356
|
-
axes: list[Axes],
|
|
357
|
-
t_min: float,
|
|
358
|
-
t_max: float,
|
|
359
|
-
time_mult: float,
|
|
360
|
-
time_unit: str,
|
|
361
|
-
title: str | None,
|
|
362
|
-
) -> None:
|
|
363
|
-
"""Finalize plot layout with labels and title.
|
|
364
|
-
|
|
365
|
-
Args:
|
|
366
|
-
axes: List of axes.
|
|
367
|
-
t_min: Minimum time value.
|
|
368
|
-
t_max: Maximum time value.
|
|
369
|
-
time_mult: Time unit multiplier.
|
|
370
|
-
time_unit: Time unit string.
|
|
371
|
-
title: Optional plot title.
|
|
372
|
-
"""
|
|
373
|
-
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
374
|
-
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
375
|
-
|
|
376
|
-
fig = axes[0].get_figure()
|
|
377
|
-
if fig and hasattr(fig, "tight_layout"):
|
|
378
|
-
if title:
|
|
379
|
-
fig.suptitle(title, fontsize=14, y=0.98)
|
|
380
|
-
fig.tight_layout()
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
def plot_uart_decode(
|
|
384
|
-
packets: list[ProtocolPacket],
|
|
385
|
-
*,
|
|
386
|
-
rx_trace: DigitalTrace | None = None,
|
|
387
|
-
tx_trace: DigitalTrace | None = None,
|
|
388
|
-
time_range: tuple[float, float] | None = None,
|
|
389
|
-
time_unit: str = "auto",
|
|
390
|
-
show_parity_errors: bool = True,
|
|
391
|
-
show_framing_errors: bool = True,
|
|
392
|
-
figsize: tuple[float, float] | None = None,
|
|
393
|
-
title: str = "UART Communication",
|
|
394
|
-
) -> Figure:
|
|
395
|
-
"""Plot UART decoded packets with RX/TX lanes.
|
|
396
|
-
|
|
397
|
-
Specialized visualization for UART showing separate RX and TX channels
|
|
398
|
-
with decoded bytes and error highlighting.
|
|
399
|
-
|
|
400
|
-
Args:
|
|
401
|
-
packets: List of UART packets.
|
|
402
|
-
rx_trace: Optional RX digital trace.
|
|
403
|
-
tx_trace: Optional TX digital trace.
|
|
404
|
-
time_range: Time range to plot (t_min, t_max) in seconds.
|
|
405
|
-
time_unit: Time unit for x-axis ("s", "ms", "us", "ns", "auto").
|
|
406
|
-
show_parity_errors: Highlight parity errors.
|
|
407
|
-
show_framing_errors: Highlight framing errors.
|
|
408
|
-
figsize: Figure size (width, height).
|
|
409
|
-
title: Plot title.
|
|
410
|
-
|
|
411
|
-
Returns:
|
|
412
|
-
Matplotlib Figure object.
|
|
413
|
-
|
|
414
|
-
Raises:
|
|
415
|
-
ImportError: If matplotlib is not installed.
|
|
416
|
-
ValueError: If packets list is empty.
|
|
417
|
-
|
|
418
|
-
Example:
|
|
419
|
-
>>> decoder = UARTDecoder(baudrate=115200, parity="even")
|
|
420
|
-
>>> packets = list(decoder.decode(rx_trace))
|
|
421
|
-
>>> fig = plot_uart_decode(packets, rx_trace=rx_trace, time_unit="ms")
|
|
422
|
-
"""
|
|
423
|
-
if not HAS_MATPLOTLIB:
|
|
424
|
-
raise ImportError("matplotlib is required for visualization")
|
|
425
|
-
|
|
426
|
-
if len(packets) == 0:
|
|
427
|
-
raise ValueError("packets list cannot be empty")
|
|
428
|
-
|
|
429
|
-
# If we have both RX and TX, create dual-channel visualization
|
|
430
|
-
if rx_trace is not None and tx_trace is not None:
|
|
431
|
-
return _plot_dual_channel_uart(
|
|
432
|
-
packets,
|
|
433
|
-
rx_trace=rx_trace,
|
|
434
|
-
tx_trace=tx_trace,
|
|
435
|
-
time_range=time_range,
|
|
436
|
-
time_unit=time_unit,
|
|
437
|
-
show_parity_errors=show_parity_errors,
|
|
438
|
-
show_framing_errors=show_framing_errors,
|
|
439
|
-
figsize=figsize,
|
|
440
|
-
title=title,
|
|
441
|
-
)
|
|
442
|
-
|
|
443
|
-
# Single-channel view using generic decode plot
|
|
444
|
-
return plot_protocol_decode(
|
|
445
|
-
packets,
|
|
446
|
-
trace=rx_trace or tx_trace,
|
|
447
|
-
trace_channel="RX" if rx_trace else "TX",
|
|
448
|
-
show_errors=show_parity_errors or show_framing_errors,
|
|
449
|
-
time_range=time_range,
|
|
450
|
-
time_unit=time_unit,
|
|
451
|
-
figsize=figsize,
|
|
452
|
-
title=title,
|
|
453
|
-
)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
def _plot_dual_channel_uart(
|
|
457
|
-
packets: list[ProtocolPacket],
|
|
458
|
-
*,
|
|
459
|
-
rx_trace: DigitalTrace,
|
|
460
|
-
tx_trace: DigitalTrace,
|
|
461
|
-
time_range: tuple[float, float] | None = None,
|
|
462
|
-
time_unit: str = "auto",
|
|
463
|
-
show_parity_errors: bool = True,
|
|
464
|
-
show_framing_errors: bool = True,
|
|
465
|
-
figsize: tuple[float, float] | None = None,
|
|
466
|
-
title: str = "UART Communication",
|
|
467
|
-
) -> Figure:
|
|
468
|
-
"""Create dual-channel UART visualization with separate RX/TX rows.
|
|
469
|
-
|
|
470
|
-
Args:
|
|
471
|
-
packets: List of UART packets (may include both RX and TX).
|
|
472
|
-
rx_trace: RX digital trace.
|
|
473
|
-
tx_trace: TX digital trace.
|
|
474
|
-
time_range: Time range to plot (t_min, t_max) in seconds.
|
|
475
|
-
time_unit: Time unit for x-axis.
|
|
476
|
-
show_parity_errors: Highlight parity errors.
|
|
477
|
-
show_framing_errors: Highlight framing errors.
|
|
478
|
-
figsize: Figure size (width, height).
|
|
479
|
-
title: Plot title.
|
|
480
|
-
|
|
481
|
-
Returns:
|
|
482
|
-
Matplotlib Figure object.
|
|
483
|
-
"""
|
|
484
|
-
# Calculate time parameters
|
|
485
|
-
t_min, t_max, time_mult, time_unit = _determine_time_params(packets, time_range, time_unit)
|
|
486
|
-
|
|
487
|
-
# Create figure with 4 rows
|
|
488
|
-
fig, axes = _create_dual_uart_figure(figsize)
|
|
489
|
-
|
|
490
|
-
# Separate packets by channel
|
|
491
|
-
rx_packets, tx_packets = _separate_uart_packets(packets)
|
|
492
|
-
show_errors = show_parity_errors or show_framing_errors
|
|
493
|
-
|
|
494
|
-
# Plot all four rows
|
|
495
|
-
_plot_uart_channel_pair(
|
|
496
|
-
axes[0], axes[1], rx_trace, rx_packets, "RX", t_min, t_max, time_mult, show_errors
|
|
497
|
-
)
|
|
498
|
-
_plot_uart_channel_pair(
|
|
499
|
-
axes[2], axes[3], tx_trace, tx_packets, "TX", t_min, t_max, time_mult, show_errors
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
# Finalize plot
|
|
503
|
-
_finalize_uart_plot(fig, axes, t_min, t_max, time_mult, time_unit, title)
|
|
504
|
-
|
|
505
|
-
return fig
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
def _create_dual_uart_figure(
|
|
509
|
-
figsize: tuple[float, float] | None,
|
|
510
|
-
) -> tuple[Figure, list[Axes]]:
|
|
511
|
-
"""Create figure for dual-channel UART plot.
|
|
512
|
-
|
|
513
|
-
Args:
|
|
514
|
-
figsize: Figure size or None for auto-calculation.
|
|
515
|
-
|
|
516
|
-
Returns:
|
|
517
|
-
Tuple of (figure, axes_list).
|
|
518
|
-
"""
|
|
519
|
-
n_rows = 4
|
|
520
|
-
|
|
521
|
-
if figsize is None:
|
|
522
|
-
width = 14
|
|
523
|
-
height = max(6, n_rows * 1.2 + 1)
|
|
524
|
-
figsize = (width, height)
|
|
525
|
-
|
|
526
|
-
fig, axes = plt.subplots(
|
|
527
|
-
n_rows,
|
|
528
|
-
1,
|
|
529
|
-
figsize=figsize,
|
|
530
|
-
sharex=True,
|
|
531
|
-
gridspec_kw={"hspace": 0.1, "height_ratios": [1, 0.8, 1, 0.8]},
|
|
532
|
-
)
|
|
533
|
-
|
|
534
|
-
return fig, axes
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def _separate_uart_packets(
|
|
538
|
-
packets: list[ProtocolPacket],
|
|
539
|
-
) -> tuple[list[ProtocolPacket], list[ProtocolPacket]]:
|
|
540
|
-
"""Separate UART packets by channel (RX vs TX).
|
|
541
|
-
|
|
542
|
-
Args:
|
|
543
|
-
packets: List of UART packets.
|
|
544
|
-
|
|
545
|
-
Returns:
|
|
546
|
-
Tuple of (rx_packets, tx_packets).
|
|
547
|
-
"""
|
|
548
|
-
rx_packets = []
|
|
549
|
-
tx_packets = []
|
|
550
|
-
|
|
551
|
-
for packet in packets:
|
|
552
|
-
channel = getattr(packet, "channel", None)
|
|
553
|
-
if channel is None and hasattr(packet, "metadata"):
|
|
554
|
-
channel = packet.metadata.get("channel") if isinstance(packet.metadata, dict) else None
|
|
555
|
-
|
|
556
|
-
if channel == "TX":
|
|
557
|
-
tx_packets.append(packet)
|
|
558
|
-
else:
|
|
559
|
-
rx_packets.append(packet)
|
|
560
|
-
|
|
561
|
-
# If no channel info, put all packets on RX
|
|
562
|
-
if not rx_packets and not tx_packets:
|
|
563
|
-
rx_packets = packets
|
|
564
|
-
|
|
565
|
-
return rx_packets, tx_packets
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
def _plot_uart_channel_pair(
|
|
569
|
-
ax_wave: Axes,
|
|
570
|
-
ax_packets: Axes,
|
|
571
|
-
trace: DigitalTrace,
|
|
572
|
-
packets: list[ProtocolPacket],
|
|
573
|
-
label: str,
|
|
574
|
-
t_min: float,
|
|
575
|
-
t_max: float,
|
|
576
|
-
time_mult: float,
|
|
577
|
-
show_errors: bool,
|
|
578
|
-
) -> None:
|
|
579
|
-
"""Plot waveform and packet row for a single UART channel.
|
|
580
|
-
|
|
581
|
-
Args:
|
|
582
|
-
ax_wave: Axis for waveform plot.
|
|
583
|
-
ax_packets: Axis for packet annotations.
|
|
584
|
-
trace: Digital trace for the channel.
|
|
585
|
-
packets: Packets for this channel.
|
|
586
|
-
label: Channel label (e.g., "RX" or "TX").
|
|
587
|
-
t_min: Minimum time value.
|
|
588
|
-
t_max: Maximum time value.
|
|
589
|
-
time_mult: Time unit multiplier.
|
|
590
|
-
show_errors: Whether to highlight errors.
|
|
591
|
-
"""
|
|
592
|
-
# Plot waveform
|
|
593
|
-
trace_time = trace.time_vector * time_mult
|
|
594
|
-
trace_data = trace.data.astype(float)
|
|
595
|
-
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
596
|
-
_plot_digital_waveform(ax_wave, trace_time[mask], trace_data[mask])
|
|
597
|
-
ax_wave.set_ylabel(label, rotation=0, ha="right", va="center", fontsize=10)
|
|
598
|
-
ax_wave.set_ylim(-0.2, 1.3)
|
|
599
|
-
ax_wave.set_yticks([])
|
|
600
|
-
ax_wave.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
601
|
-
|
|
602
|
-
# Plot packets
|
|
603
|
-
_plot_packet_row(ax_packets, packets, t_min, t_max, time_mult, show_errors)
|
|
604
|
-
ax_packets.set_ylabel(f"{label}\nData", rotation=0, ha="right", va="center", fontsize=9)
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
def _finalize_uart_plot(
|
|
608
|
-
fig: Figure,
|
|
609
|
-
axes: list[Axes],
|
|
610
|
-
t_min: float,
|
|
611
|
-
t_max: float,
|
|
612
|
-
time_mult: float,
|
|
613
|
-
time_unit: str,
|
|
614
|
-
title: str | None,
|
|
615
|
-
) -> None:
|
|
616
|
-
"""Add final formatting to UART plot.
|
|
617
|
-
|
|
618
|
-
Args:
|
|
619
|
-
fig: Matplotlib figure.
|
|
620
|
-
axes: List of axes.
|
|
621
|
-
t_min: Minimum time value.
|
|
622
|
-
t_max: Maximum time value.
|
|
623
|
-
time_mult: Time multiplier.
|
|
624
|
-
time_unit: Time unit string.
|
|
625
|
-
title: Plot title or None.
|
|
626
|
-
"""
|
|
627
|
-
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
628
|
-
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
629
|
-
|
|
630
|
-
if title:
|
|
631
|
-
fig.suptitle(title, fontsize=14, y=0.98)
|
|
632
|
-
|
|
633
|
-
fig.tight_layout()
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
def _plot_packet_row(
|
|
637
|
-
ax: Axes,
|
|
638
|
-
packets: list[ProtocolPacket],
|
|
639
|
-
t_min: float,
|
|
640
|
-
t_max: float,
|
|
641
|
-
time_mult: float,
|
|
642
|
-
show_errors: bool,
|
|
643
|
-
) -> None:
|
|
644
|
-
"""Plot a single row of packets on the given axes."""
|
|
645
|
-
for packet in packets:
|
|
646
|
-
if packet.timestamp < t_min or packet.timestamp > t_max:
|
|
647
|
-
continue
|
|
648
|
-
|
|
649
|
-
start = packet.timestamp * time_mult
|
|
650
|
-
end = (
|
|
651
|
-
packet.end_timestamp if packet.end_timestamp else packet.timestamp + 0.001
|
|
652
|
-
) * time_mult
|
|
653
|
-
|
|
654
|
-
# Determine packet color
|
|
655
|
-
if show_errors and packet.errors:
|
|
656
|
-
color = "#ff6b6b" # Red for errors
|
|
657
|
-
else:
|
|
658
|
-
color = "#4ecdc4" # Teal
|
|
659
|
-
|
|
660
|
-
# Draw packet rectangle
|
|
661
|
-
rect = patches.Rectangle(
|
|
662
|
-
(start, 0.1),
|
|
663
|
-
end - start,
|
|
664
|
-
0.8,
|
|
665
|
-
facecolor=color,
|
|
666
|
-
edgecolor="black",
|
|
667
|
-
linewidth=0.8,
|
|
668
|
-
alpha=0.7,
|
|
669
|
-
)
|
|
670
|
-
ax.add_patch(rect)
|
|
671
|
-
|
|
672
|
-
# Add data annotation
|
|
673
|
-
if packet.data:
|
|
674
|
-
data_str = _format_packet_data(packet)
|
|
675
|
-
mid_time = (start + end) / 2
|
|
676
|
-
ax.text(
|
|
677
|
-
mid_time,
|
|
678
|
-
0.5,
|
|
679
|
-
data_str,
|
|
680
|
-
ha="center",
|
|
681
|
-
va="center",
|
|
682
|
-
fontsize=7,
|
|
683
|
-
fontweight="bold",
|
|
684
|
-
color="white" if not (show_errors and packet.errors) else "black",
|
|
685
|
-
)
|
|
686
|
-
|
|
687
|
-
# Add error markers
|
|
688
|
-
if show_errors and packet.errors:
|
|
689
|
-
ax.plot(start, 1.1, "rx", markersize=6, markeredgewidth=2)
|
|
690
|
-
|
|
691
|
-
ax.set_ylim(0, 1.2)
|
|
692
|
-
ax.set_yticks([])
|
|
693
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
def plot_spi_decode(
|
|
697
|
-
packets: list[ProtocolPacket],
|
|
698
|
-
*,
|
|
699
|
-
clk_trace: DigitalTrace | None = None,
|
|
700
|
-
mosi_trace: DigitalTrace | None = None,
|
|
701
|
-
miso_trace: DigitalTrace | None = None,
|
|
702
|
-
cs_trace: DigitalTrace | None = None,
|
|
703
|
-
time_range: tuple[float, float] | None = None,
|
|
704
|
-
time_unit: str = "auto",
|
|
705
|
-
show_mosi: bool = True,
|
|
706
|
-
show_miso: bool = True,
|
|
707
|
-
figsize: tuple[float, float] | None = None,
|
|
708
|
-
title: str = "SPI Transaction",
|
|
709
|
-
) -> Figure:
|
|
710
|
-
"""Plot SPI decoded packets with CLK, MOSI, MISO, CS signals.
|
|
711
|
-
|
|
712
|
-
Specialized visualization for SPI showing all relevant signals
|
|
713
|
-
and decoded words on MOSI/MISO channels.
|
|
714
|
-
|
|
715
|
-
Args:
|
|
716
|
-
packets: List of SPI packets.
|
|
717
|
-
clk_trace: Optional clock signal trace.
|
|
718
|
-
mosi_trace: Optional MOSI (Master Out Slave In) trace.
|
|
719
|
-
miso_trace: Optional MISO (Master In Slave Out) trace.
|
|
720
|
-
cs_trace: Optional chip select trace.
|
|
721
|
-
time_range: Time range to plot (t_min, t_max) in seconds.
|
|
722
|
-
time_unit: Time unit for x-axis.
|
|
723
|
-
show_mosi: Show MOSI decoded data.
|
|
724
|
-
show_miso: Show MISO decoded data.
|
|
725
|
-
figsize: Figure size.
|
|
726
|
-
title: Plot title.
|
|
727
|
-
|
|
728
|
-
Returns:
|
|
729
|
-
Matplotlib Figure object.
|
|
730
|
-
|
|
731
|
-
Raises:
|
|
732
|
-
ImportError: If matplotlib is not installed.
|
|
733
|
-
ValueError: If packets list is empty.
|
|
734
|
-
|
|
735
|
-
Example:
|
|
736
|
-
>>> decoder = SPIDecoder(cpol=0, cpha=0, word_size=8)
|
|
737
|
-
>>> packets = list(decoder.decode(clk=clk, mosi=mosi, miso=miso))
|
|
738
|
-
>>> fig = plot_spi_decode(packets, clk_trace=clk, mosi_trace=mosi)
|
|
739
|
-
"""
|
|
740
|
-
if not HAS_MATPLOTLIB:
|
|
741
|
-
raise ImportError("matplotlib is required for visualization")
|
|
742
|
-
|
|
743
|
-
if len(packets) == 0:
|
|
744
|
-
raise ValueError("packets list cannot be empty")
|
|
745
|
-
|
|
746
|
-
# If we have multiple traces, create multi-channel visualization
|
|
747
|
-
traces_available = sum(
|
|
748
|
-
1 for t in [clk_trace, mosi_trace, miso_trace, cs_trace] if t is not None
|
|
749
|
-
)
|
|
750
|
-
|
|
751
|
-
if traces_available >= 2:
|
|
752
|
-
return _plot_multi_channel_spi(
|
|
753
|
-
packets,
|
|
754
|
-
clk_trace=clk_trace,
|
|
755
|
-
mosi_trace=mosi_trace,
|
|
756
|
-
miso_trace=miso_trace,
|
|
757
|
-
cs_trace=cs_trace,
|
|
758
|
-
time_range=time_range,
|
|
759
|
-
time_unit=time_unit,
|
|
760
|
-
show_mosi=show_mosi,
|
|
761
|
-
show_miso=show_miso,
|
|
762
|
-
figsize=figsize,
|
|
763
|
-
title=title,
|
|
764
|
-
)
|
|
765
|
-
|
|
766
|
-
# Single-channel view using generic decode plot
|
|
767
|
-
return plot_protocol_decode(
|
|
768
|
-
packets,
|
|
769
|
-
trace=mosi_trace,
|
|
770
|
-
trace_channel="MOSI",
|
|
771
|
-
time_range=time_range,
|
|
772
|
-
time_unit=time_unit,
|
|
773
|
-
figsize=figsize,
|
|
774
|
-
title=title,
|
|
775
|
-
)
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
def _plot_multi_channel_spi(
|
|
779
|
-
packets: list[ProtocolPacket],
|
|
780
|
-
*,
|
|
781
|
-
clk_trace: DigitalTrace | None = None,
|
|
782
|
-
mosi_trace: DigitalTrace | None = None,
|
|
783
|
-
miso_trace: DigitalTrace | None = None,
|
|
784
|
-
cs_trace: DigitalTrace | None = None,
|
|
785
|
-
time_range: tuple[float, float] | None = None,
|
|
786
|
-
time_unit: str = "auto",
|
|
787
|
-
show_mosi: bool = True,
|
|
788
|
-
show_miso: bool = True,
|
|
789
|
-
figsize: tuple[float, float] | None = None,
|
|
790
|
-
title: str = "SPI Transaction",
|
|
791
|
-
) -> Figure:
|
|
792
|
-
"""Create multi-channel SPI visualization with separate rows for each signal.
|
|
793
|
-
|
|
794
|
-
Args:
|
|
795
|
-
packets: List of SPI packets.
|
|
796
|
-
clk_trace: Optional clock signal trace.
|
|
797
|
-
mosi_trace: Optional MOSI trace.
|
|
798
|
-
miso_trace: Optional MISO trace.
|
|
799
|
-
cs_trace: Optional chip select trace.
|
|
800
|
-
time_range: Time range to plot.
|
|
801
|
-
time_unit: Time unit for x-axis.
|
|
802
|
-
show_mosi: Show MOSI decoded data row.
|
|
803
|
-
show_miso: Show MISO decoded data row.
|
|
804
|
-
figsize: Figure size.
|
|
805
|
-
title: Plot title.
|
|
806
|
-
|
|
807
|
-
Returns:
|
|
808
|
-
Matplotlib Figure object.
|
|
809
|
-
"""
|
|
810
|
-
t_min, t_max, time_mult, time_unit = _determine_time_params(packets, time_range, time_unit)
|
|
811
|
-
rows = _build_spi_row_list(cs_trace, clk_trace, mosi_trace, miso_trace, show_mosi, show_miso)
|
|
812
|
-
|
|
813
|
-
if len(rows) == 0:
|
|
814
|
-
return plot_protocol_decode(
|
|
815
|
-
packets, time_range=(t_min, t_max), time_unit=time_unit, figsize=figsize, title=title
|
|
816
|
-
)
|
|
817
|
-
|
|
818
|
-
fig, axes = _create_spi_figure(rows, figsize)
|
|
819
|
-
mosi_packets, miso_packets = _separate_spi_packets(packets)
|
|
820
|
-
_render_spi_rows(axes, rows, t_min, t_max, time_mult, mosi_packets, miso_packets)
|
|
821
|
-
_finalize_spi_plot(axes, t_min, t_max, time_mult, time_unit, title)
|
|
822
|
-
|
|
823
|
-
return fig
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
def _determine_time_params(
|
|
827
|
-
packets: list[ProtocolPacket],
|
|
828
|
-
time_range: tuple[float, float] | None,
|
|
829
|
-
time_unit: str,
|
|
830
|
-
) -> tuple[float, float, float, str]:
|
|
831
|
-
"""Determine time range and multiplier for SPI plot.
|
|
832
|
-
|
|
833
|
-
Args:
|
|
834
|
-
packets: List of SPI packets for time range calculation.
|
|
835
|
-
time_range: User-specified time range or None for auto.
|
|
836
|
-
time_unit: Time unit ("auto" or specific unit).
|
|
837
|
-
|
|
838
|
-
Returns:
|
|
839
|
-
Tuple of (t_min, t_max, time_mult, time_unit).
|
|
840
|
-
"""
|
|
841
|
-
if time_range is None:
|
|
842
|
-
t_min = min(p.timestamp for p in packets)
|
|
843
|
-
t_max = max(p.end_timestamp if p.end_timestamp else p.timestamp for p in packets)
|
|
844
|
-
padding = (t_max - t_min) * 0.1
|
|
845
|
-
t_min -= padding
|
|
846
|
-
t_max += padding
|
|
847
|
-
else:
|
|
848
|
-
t_min, t_max = time_range
|
|
849
|
-
|
|
850
|
-
if time_unit == "auto":
|
|
851
|
-
time_range_val = t_max - t_min
|
|
852
|
-
if time_range_val < 1e-6:
|
|
853
|
-
time_unit = "ns"
|
|
854
|
-
time_mult = 1e9
|
|
855
|
-
elif time_range_val < 1e-3:
|
|
856
|
-
time_unit = "us"
|
|
857
|
-
time_mult = 1e6
|
|
858
|
-
elif time_range_val < 1:
|
|
859
|
-
time_unit = "ms"
|
|
860
|
-
time_mult = 1e3
|
|
861
|
-
else:
|
|
862
|
-
time_unit = "s"
|
|
863
|
-
time_mult = 1.0
|
|
864
|
-
else:
|
|
865
|
-
time_mult = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
866
|
-
|
|
867
|
-
return t_min, t_max, time_mult, time_unit
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
def _build_spi_row_list(
|
|
871
|
-
cs_trace: DigitalTrace | None,
|
|
872
|
-
clk_trace: DigitalTrace | None,
|
|
873
|
-
mosi_trace: DigitalTrace | None,
|
|
874
|
-
miso_trace: DigitalTrace | None,
|
|
875
|
-
show_mosi: bool,
|
|
876
|
-
show_miso: bool,
|
|
877
|
-
) -> list[dict[str, Any]]:
|
|
878
|
-
"""Build list of row specifications for SPI multi-channel plot.
|
|
879
|
-
|
|
880
|
-
Args:
|
|
881
|
-
cs_trace: Chip select trace.
|
|
882
|
-
clk_trace: Clock trace.
|
|
883
|
-
mosi_trace: MOSI trace.
|
|
884
|
-
miso_trace: MISO trace.
|
|
885
|
-
show_mosi: Whether to show MOSI data row.
|
|
886
|
-
show_miso: Whether to show MISO data row.
|
|
887
|
-
|
|
888
|
-
Returns:
|
|
889
|
-
List of row dictionaries specifying type, trace, label, channel.
|
|
890
|
-
"""
|
|
891
|
-
rows: list[dict[str, Any]] = []
|
|
892
|
-
|
|
893
|
-
if cs_trace is not None:
|
|
894
|
-
rows.append({"type": "waveform", "trace": cs_trace, "label": "CS"})
|
|
895
|
-
if clk_trace is not None:
|
|
896
|
-
rows.append({"type": "waveform", "trace": clk_trace, "label": "CLK"})
|
|
897
|
-
if mosi_trace is not None:
|
|
898
|
-
rows.append({"type": "waveform", "trace": mosi_trace, "label": "MOSI"})
|
|
899
|
-
if show_mosi:
|
|
900
|
-
rows.append({"type": "packets", "label": "MOSI\nData", "channel": "MOSI"})
|
|
901
|
-
if miso_trace is not None:
|
|
902
|
-
rows.append({"type": "waveform", "trace": miso_trace, "label": "MISO"})
|
|
903
|
-
if show_miso:
|
|
904
|
-
rows.append({"type": "packets", "label": "MISO\nData", "channel": "MISO"})
|
|
905
|
-
|
|
906
|
-
return rows
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
def _create_spi_figure(
|
|
910
|
-
rows: list[dict[str, Any]],
|
|
911
|
-
figsize: tuple[float, float] | None,
|
|
912
|
-
) -> tuple[Figure, list[Axes]]:
|
|
913
|
-
"""Create matplotlib figure and axes for SPI plot.
|
|
914
|
-
|
|
915
|
-
Args:
|
|
916
|
-
rows: Row specifications from _build_spi_row_list.
|
|
917
|
-
figsize: Figure size or None for auto-calculation.
|
|
918
|
-
|
|
919
|
-
Returns:
|
|
920
|
-
Tuple of (figure, axes_list).
|
|
921
|
-
"""
|
|
922
|
-
height_ratios = [1.0 if row["type"] == "waveform" else 0.6 for row in rows]
|
|
923
|
-
n_rows = len(rows)
|
|
924
|
-
|
|
925
|
-
if figsize is None:
|
|
926
|
-
width = 14
|
|
927
|
-
height = max(4, sum(height_ratios) * 1.2 + 1)
|
|
928
|
-
figsize = (width, height)
|
|
929
|
-
|
|
930
|
-
fig, axes = plt.subplots(
|
|
931
|
-
n_rows,
|
|
932
|
-
1,
|
|
933
|
-
figsize=figsize,
|
|
934
|
-
sharex=True,
|
|
935
|
-
gridspec_kw={"hspace": 0.1, "height_ratios": height_ratios},
|
|
936
|
-
)
|
|
937
|
-
if n_rows == 1:
|
|
938
|
-
axes = [axes]
|
|
939
|
-
|
|
940
|
-
return fig, axes
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
def _separate_spi_packets(
|
|
944
|
-
packets: list[ProtocolPacket],
|
|
945
|
-
) -> tuple[list[ProtocolPacket], list[ProtocolPacket]]:
|
|
946
|
-
"""Separate packets by channel (MOSI vs MISO).
|
|
947
|
-
|
|
948
|
-
Args:
|
|
949
|
-
packets: List of SPI packets.
|
|
950
|
-
|
|
951
|
-
Returns:
|
|
952
|
-
Tuple of (mosi_packets, miso_packets).
|
|
953
|
-
"""
|
|
954
|
-
mosi_packets = []
|
|
955
|
-
miso_packets = []
|
|
956
|
-
|
|
957
|
-
for packet in packets:
|
|
958
|
-
channel = getattr(packet, "channel", None)
|
|
959
|
-
if channel is None and hasattr(packet, "metadata"):
|
|
960
|
-
channel = packet.metadata.get("channel") if isinstance(packet.metadata, dict) else None
|
|
961
|
-
|
|
962
|
-
if channel == "MISO":
|
|
963
|
-
miso_packets.append(packet)
|
|
964
|
-
else:
|
|
965
|
-
mosi_packets.append(packet)
|
|
966
|
-
|
|
967
|
-
if not mosi_packets and not miso_packets:
|
|
968
|
-
mosi_packets = packets
|
|
969
|
-
|
|
970
|
-
return mosi_packets, miso_packets
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
def _render_spi_rows(
|
|
974
|
-
axes: list[Axes],
|
|
975
|
-
rows: list[dict[str, Any]],
|
|
976
|
-
t_min: float,
|
|
977
|
-
t_max: float,
|
|
978
|
-
time_mult: float,
|
|
979
|
-
mosi_packets: list[ProtocolPacket],
|
|
980
|
-
miso_packets: list[ProtocolPacket],
|
|
981
|
-
) -> None:
|
|
982
|
-
"""Render all SPI plot rows (waveforms and packet data).
|
|
983
|
-
|
|
984
|
-
Args:
|
|
985
|
-
axes: List of matplotlib axes.
|
|
986
|
-
rows: Row specifications.
|
|
987
|
-
t_min: Minimum time value.
|
|
988
|
-
t_max: Maximum time value.
|
|
989
|
-
time_mult: Time multiplier for unit conversion.
|
|
990
|
-
mosi_packets: MOSI channel packets.
|
|
991
|
-
miso_packets: MISO channel packets.
|
|
992
|
-
"""
|
|
993
|
-
for ax, row in zip(axes, rows, strict=False):
|
|
994
|
-
if row["type"] == "waveform":
|
|
995
|
-
_render_spi_waveform_row(ax, row, t_min, t_max, time_mult)
|
|
996
|
-
else:
|
|
997
|
-
_render_spi_packet_row(ax, row, t_min, t_max, time_mult, mosi_packets, miso_packets)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
def _render_spi_waveform_row(
|
|
1001
|
-
ax: Axes,
|
|
1002
|
-
row: dict[str, Any],
|
|
1003
|
-
t_min: float,
|
|
1004
|
-
t_max: float,
|
|
1005
|
-
time_mult: float,
|
|
1006
|
-
) -> None:
|
|
1007
|
-
"""Render a single waveform row for SPI plot.
|
|
1008
|
-
|
|
1009
|
-
Args:
|
|
1010
|
-
ax: Matplotlib axis to render on.
|
|
1011
|
-
row: Row specification with trace and label.
|
|
1012
|
-
t_min: Minimum time value.
|
|
1013
|
-
t_max: Maximum time value.
|
|
1014
|
-
time_mult: Time multiplier for unit conversion.
|
|
1015
|
-
"""
|
|
1016
|
-
trace = row["trace"]
|
|
1017
|
-
trace_time = trace.time_vector * time_mult
|
|
1018
|
-
trace_data = trace.data.astype(float)
|
|
1019
|
-
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
1020
|
-
_plot_digital_waveform(ax, trace_time[mask], trace_data[mask])
|
|
1021
|
-
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=10)
|
|
1022
|
-
ax.set_ylim(-0.2, 1.3)
|
|
1023
|
-
ax.set_yticks([])
|
|
1024
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
def _render_spi_packet_row(
|
|
1028
|
-
ax: Axes,
|
|
1029
|
-
row: dict[str, Any],
|
|
1030
|
-
t_min: float,
|
|
1031
|
-
t_max: float,
|
|
1032
|
-
time_mult: float,
|
|
1033
|
-
mosi_packets: list[ProtocolPacket],
|
|
1034
|
-
miso_packets: list[ProtocolPacket],
|
|
1035
|
-
) -> None:
|
|
1036
|
-
"""Render a single packet data row for SPI plot.
|
|
1037
|
-
|
|
1038
|
-
Args:
|
|
1039
|
-
ax: Matplotlib axis to render on.
|
|
1040
|
-
row: Row specification with channel and label.
|
|
1041
|
-
t_min: Minimum time value.
|
|
1042
|
-
t_max: Maximum time value.
|
|
1043
|
-
time_mult: Time multiplier for unit conversion.
|
|
1044
|
-
mosi_packets: MOSI channel packets.
|
|
1045
|
-
miso_packets: MISO channel packets.
|
|
1046
|
-
"""
|
|
1047
|
-
channel = row.get("channel", "MOSI")
|
|
1048
|
-
pkts = mosi_packets if channel == "MOSI" else miso_packets
|
|
1049
|
-
_plot_packet_row(ax, pkts, t_min, t_max, time_mult, show_errors=True)
|
|
1050
|
-
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=9)
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
def _finalize_spi_plot(
|
|
1054
|
-
axes: list[Axes],
|
|
1055
|
-
t_min: float,
|
|
1056
|
-
t_max: float,
|
|
1057
|
-
time_mult: float,
|
|
1058
|
-
time_unit: str,
|
|
1059
|
-
title: str | None,
|
|
1060
|
-
) -> None:
|
|
1061
|
-
"""Add final formatting to SPI plot.
|
|
1062
|
-
|
|
1063
|
-
Args:
|
|
1064
|
-
axes: List of matplotlib axes.
|
|
1065
|
-
t_min: Minimum time value.
|
|
1066
|
-
t_max: Maximum time value.
|
|
1067
|
-
time_mult: Time multiplier for unit conversion.
|
|
1068
|
-
time_unit: Time unit string for label.
|
|
1069
|
-
title: Plot title or None.
|
|
1070
|
-
"""
|
|
1071
|
-
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
1072
|
-
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
1073
|
-
|
|
1074
|
-
fig = axes[0].get_figure()
|
|
1075
|
-
if title and fig is not None:
|
|
1076
|
-
fig.suptitle(title, fontsize=14, y=0.98)
|
|
1077
|
-
|
|
1078
|
-
if fig is not None and hasattr(fig, "tight_layout"):
|
|
1079
|
-
fig.tight_layout()
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
def plot_i2c_decode(
|
|
1083
|
-
packets: list[ProtocolPacket],
|
|
1084
|
-
*,
|
|
1085
|
-
sda_trace: DigitalTrace | None = None,
|
|
1086
|
-
scl_trace: DigitalTrace | None = None,
|
|
1087
|
-
time_range: tuple[float, float] | None = None,
|
|
1088
|
-
time_unit: str = "auto",
|
|
1089
|
-
show_addresses: bool = True,
|
|
1090
|
-
show_ack_nack: bool = True,
|
|
1091
|
-
figsize: tuple[float, float] | None = None,
|
|
1092
|
-
title: str = "I2C Transaction",
|
|
1093
|
-
) -> Figure:
|
|
1094
|
-
"""Plot I2C decoded packets with SDA/SCL and address annotations.
|
|
1095
|
-
|
|
1096
|
-
Specialized visualization for I2C showing start/stop conditions,
|
|
1097
|
-
addresses, data bytes, and ACK/NACK bits.
|
|
1098
|
-
|
|
1099
|
-
Args:
|
|
1100
|
-
packets: List of I2C packets.
|
|
1101
|
-
sda_trace: Optional SDA (data) signal trace.
|
|
1102
|
-
scl_trace: Optional SCL (clock) signal trace.
|
|
1103
|
-
time_range: Time range to plot (t_min, t_max) in seconds.
|
|
1104
|
-
time_unit: Time unit for x-axis.
|
|
1105
|
-
show_addresses: Highlight address bytes.
|
|
1106
|
-
show_ack_nack: Show ACK/NACK indicators.
|
|
1107
|
-
figsize: Figure size.
|
|
1108
|
-
title: Plot title.
|
|
1109
|
-
|
|
1110
|
-
Returns:
|
|
1111
|
-
Matplotlib Figure object.
|
|
1112
|
-
|
|
1113
|
-
Example:
|
|
1114
|
-
>>> decoder = I2CDecoder()
|
|
1115
|
-
>>> packets = list(decoder.decode(sda=sda, scl=scl))
|
|
1116
|
-
>>> fig = plot_i2c_decode(packets, sda_trace=sda, scl_trace=scl)
|
|
1117
|
-
"""
|
|
1118
|
-
return plot_protocol_decode(
|
|
1119
|
-
packets,
|
|
1120
|
-
trace=sda_trace,
|
|
1121
|
-
trace_channel="SDA",
|
|
1122
|
-
time_range=time_range,
|
|
1123
|
-
time_unit=time_unit,
|
|
1124
|
-
figsize=figsize,
|
|
1125
|
-
title=title,
|
|
1126
|
-
)
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
def plot_can_decode(
|
|
1130
|
-
packets: list[ProtocolPacket],
|
|
1131
|
-
*,
|
|
1132
|
-
can_trace: DigitalTrace | None = None,
|
|
1133
|
-
time_range: tuple[float, float] | None = None,
|
|
1134
|
-
time_unit: str = "auto",
|
|
1135
|
-
show_ids: bool = True,
|
|
1136
|
-
show_data_length: bool = True,
|
|
1137
|
-
colorize_by_id: bool = True,
|
|
1138
|
-
figsize: tuple[float, float] | None = None,
|
|
1139
|
-
title: str = "CAN Bus",
|
|
1140
|
-
) -> Figure:
|
|
1141
|
-
"""Plot CAN decoded packets with arbitration IDs and data.
|
|
1142
|
-
|
|
1143
|
-
Specialized visualization for CAN bus showing arbitration IDs,
|
|
1144
|
-
data length codes, and message data.
|
|
1145
|
-
|
|
1146
|
-
Args:
|
|
1147
|
-
packets: List of CAN packets.
|
|
1148
|
-
can_trace: Optional CAN bus trace.
|
|
1149
|
-
time_range: Time range to plot (t_min, t_max) in seconds.
|
|
1150
|
-
time_unit: Time unit for x-axis.
|
|
1151
|
-
show_ids: Show arbitration IDs in annotations.
|
|
1152
|
-
show_data_length: Show DLC (Data Length Code).
|
|
1153
|
-
colorize_by_id: Use different colors for different CAN IDs.
|
|
1154
|
-
figsize: Figure size.
|
|
1155
|
-
title: Plot title.
|
|
1156
|
-
|
|
1157
|
-
Returns:
|
|
1158
|
-
Matplotlib Figure object.
|
|
1159
|
-
|
|
1160
|
-
Example:
|
|
1161
|
-
>>> decoder = CANDecoder()
|
|
1162
|
-
>>> packets = list(decoder.decode(can_trace))
|
|
1163
|
-
>>> fig = plot_can_decode(packets, can_trace=can_trace, colorize_by_id=True)
|
|
1164
|
-
"""
|
|
1165
|
-
return plot_protocol_decode(
|
|
1166
|
-
packets,
|
|
1167
|
-
trace=can_trace,
|
|
1168
|
-
trace_channel="CAN",
|
|
1169
|
-
colorize=colorize_by_id,
|
|
1170
|
-
time_range=time_range,
|
|
1171
|
-
time_unit=time_unit,
|
|
1172
|
-
figsize=figsize,
|
|
1173
|
-
title=title,
|
|
1174
|
-
)
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
def _plot_digital_waveform(
|
|
1178
|
-
ax: Axes,
|
|
1179
|
-
time: NDArray[np.float64],
|
|
1180
|
-
data: NDArray[np.float64],
|
|
1181
|
-
) -> None:
|
|
1182
|
-
"""Plot digital waveform with clean transitions."""
|
|
1183
|
-
for i in range(len(time) - 1):
|
|
1184
|
-
level = 1 if data[i] > 0.5 else 0
|
|
1185
|
-
# Horizontal line
|
|
1186
|
-
ax.plot(
|
|
1187
|
-
[time[i], time[i + 1]],
|
|
1188
|
-
[level, level],
|
|
1189
|
-
"b-",
|
|
1190
|
-
linewidth=1.5,
|
|
1191
|
-
)
|
|
1192
|
-
# Vertical transition
|
|
1193
|
-
if i < len(time) - 1:
|
|
1194
|
-
next_level = 1 if data[i + 1] > 0.5 else 0
|
|
1195
|
-
if level != next_level:
|
|
1196
|
-
ax.plot(
|
|
1197
|
-
[time[i + 1], time[i + 1]],
|
|
1198
|
-
[level, next_level],
|
|
1199
|
-
"b-",
|
|
1200
|
-
linewidth=1.5,
|
|
1201
|
-
)
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
def _get_packet_color(packet: ProtocolPacket, protocol: str) -> str:
|
|
1205
|
-
"""Get color for packet based on protocol and type."""
|
|
1206
|
-
# Color palette for different protocols
|
|
1207
|
-
colors = {
|
|
1208
|
-
"UART": "#4ecdc4", # Teal
|
|
1209
|
-
"SPI": "#95e1d3", # Mint
|
|
1210
|
-
"I2C": "#f38181", # Coral
|
|
1211
|
-
"CAN": "#aa96da", # Purple
|
|
1212
|
-
"USB": "#fcbad3", # Pink
|
|
1213
|
-
"1-Wire": "#ffffd2", # Yellow
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
return colors.get(protocol, "#4ecdc4")
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
def _format_packet_data(packet: ProtocolPacket) -> str:
|
|
1220
|
-
"""Format packet data for display."""
|
|
1221
|
-
if len(packet.data) == 0:
|
|
1222
|
-
return ""
|
|
1223
|
-
|
|
1224
|
-
# For single byte, show as hex
|
|
1225
|
-
if len(packet.data) == 1:
|
|
1226
|
-
byte_val = packet.data[0]
|
|
1227
|
-
# Show both hex and ASCII if printable
|
|
1228
|
-
if 32 <= byte_val <= 126:
|
|
1229
|
-
return f"0x{byte_val:02X} '{chr(byte_val)}'"
|
|
1230
|
-
return f"0x{byte_val:02X}"
|
|
1231
|
-
|
|
1232
|
-
# For multiple bytes, show hex string (limit to first few bytes)
|
|
1233
|
-
if len(packet.data) <= 4:
|
|
1234
|
-
return " ".join(f"{b:02X}" for b in packet.data)
|
|
1235
|
-
|
|
1236
|
-
# For longer data, truncate
|
|
1237
|
-
return " ".join(f"{b:02X}" for b in packet.data[:3]) + "..."
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
__all__ = [
|
|
1241
|
-
"plot_can_decode",
|
|
1242
|
-
"plot_i2c_decode",
|
|
1243
|
-
"plot_protocol_decode",
|
|
1244
|
-
"plot_spi_decode",
|
|
1245
|
-
"plot_uart_decode",
|
|
1246
|
-
]
|