oscura 0.7.0__py3-none-any.whl → 0.10.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/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/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- 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/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- 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 +102 -17
- 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/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- 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/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/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- 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 +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- 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.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
"""Protocol decoder handlers for pipeline system.
|
|
2
|
+
|
|
3
|
+
This module provides handlers for decoding various communication protocols.
|
|
4
|
+
All handlers follow the standard signature: (inputs, params, step_name) -> outputs.
|
|
5
|
+
|
|
6
|
+
Available Handlers:
|
|
7
|
+
- decoder.uart: Decode UART/RS232 serial protocol
|
|
8
|
+
- decoder.spi: Decode SPI (Serial Peripheral Interface)
|
|
9
|
+
- decoder.i2c: Decode I2C (Inter-Integrated Circuit)
|
|
10
|
+
- decoder.can: Decode CAN bus protocol
|
|
11
|
+
- decoder.can_fd: Decode CAN-FD protocol
|
|
12
|
+
- decoder.lin: Decode LIN bus protocol
|
|
13
|
+
- decoder.flexray: Decode FlexRay protocol
|
|
14
|
+
- decoder.onewire: Decode 1-Wire protocol
|
|
15
|
+
- decoder.manchester: Decode Manchester encoded data
|
|
16
|
+
- decoder.i2s: Decode I2S audio protocol
|
|
17
|
+
- decoder.jtag: Decode JTAG debug interface
|
|
18
|
+
- decoder.swd: Decode SWD (Serial Wire Debug)
|
|
19
|
+
- decoder.usb: Decode USB protocol
|
|
20
|
+
- decoder.hdlc: Decode HDLC frames
|
|
21
|
+
- decoder.auto: Auto-detect and decode protocol
|
|
22
|
+
- decoder.multi_protocol: Decode multiple protocols from multi-channel
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from oscura.core.config.pipeline import PipelineExecutionError
|
|
30
|
+
from oscura.pipeline.handlers import register_handler
|
|
31
|
+
|
|
32
|
+
# Lazy imports
|
|
33
|
+
_protocols = None
|
|
34
|
+
_convenience = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_protocols() -> Any:
|
|
38
|
+
"""Lazy import protocols module."""
|
|
39
|
+
global _protocols
|
|
40
|
+
if _protocols is None:
|
|
41
|
+
from oscura.analyzers import protocols as _protocols_module
|
|
42
|
+
|
|
43
|
+
_protocols = _protocols_module
|
|
44
|
+
return _protocols
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_convenience() -> Any:
|
|
48
|
+
"""Lazy import convenience module."""
|
|
49
|
+
global _convenience
|
|
50
|
+
if _convenience is None:
|
|
51
|
+
import oscura.convenience as _convenience_module
|
|
52
|
+
|
|
53
|
+
_convenience = _convenience_module
|
|
54
|
+
return _convenience
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@register_handler("decoder.uart")
|
|
58
|
+
def handle_decoder_uart(
|
|
59
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
60
|
+
) -> dict[str, Any]:
|
|
61
|
+
"""Decode UART/RS232 serial protocol.
|
|
62
|
+
|
|
63
|
+
Inputs:
|
|
64
|
+
trace: WaveformTrace or DigitalTrace to decode
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
baud_rate (int): Baud rate in bps (e.g., 9600, 115200)
|
|
68
|
+
data_bits (int, optional): Number of data bits (default: 8)
|
|
69
|
+
parity (str, optional): Parity type ('none', 'even', 'odd') (default: 'none')
|
|
70
|
+
stop_bits (float, optional): Number of stop bits (default: 1.0)
|
|
71
|
+
invert (bool, optional): Invert signal polarity (default: False)
|
|
72
|
+
|
|
73
|
+
Outputs:
|
|
74
|
+
frames: List of decoded UART frames
|
|
75
|
+
num_frames: Number of frames decoded
|
|
76
|
+
errors: List of errors encountered
|
|
77
|
+
statistics: Decoding statistics dict
|
|
78
|
+
"""
|
|
79
|
+
protocols = _get_protocols()
|
|
80
|
+
|
|
81
|
+
trace = inputs.get("trace")
|
|
82
|
+
if trace is None:
|
|
83
|
+
raise PipelineExecutionError(
|
|
84
|
+
"Missing required input 'trace'. Suggestion: Check step inputs",
|
|
85
|
+
step_name=step_name,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
baud_rate = params.get("baud_rate")
|
|
89
|
+
if baud_rate is None:
|
|
90
|
+
raise PipelineExecutionError(
|
|
91
|
+
"Missing required parameter 'baud_rate'. Suggestion: Add 'baud_rate: 115200' to params",
|
|
92
|
+
step_name=step_name,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
data_bits = params.get("data_bits", 8)
|
|
96
|
+
parity = params.get("parity", "none")
|
|
97
|
+
stop_bits = params.get("stop_bits", 1.0)
|
|
98
|
+
invert = params.get("invert", False)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
decoder = protocols.UARTDecoder(
|
|
102
|
+
baud_rate=baud_rate,
|
|
103
|
+
data_bits=data_bits,
|
|
104
|
+
parity=parity,
|
|
105
|
+
stop_bits=stop_bits,
|
|
106
|
+
invert=invert,
|
|
107
|
+
)
|
|
108
|
+
frames = decoder.decode(trace)
|
|
109
|
+
|
|
110
|
+
# Compute statistics
|
|
111
|
+
error_count = sum(1 for f in frames if getattr(f, "error", False))
|
|
112
|
+
statistics = {
|
|
113
|
+
"total_frames": len(frames),
|
|
114
|
+
"errors": error_count,
|
|
115
|
+
"success_rate": (len(frames) - error_count) / len(frames) if frames else 0,
|
|
116
|
+
}
|
|
117
|
+
errors = [f"Frame {i}: error" for i, f in enumerate(frames) if getattr(f, "error", False)]
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise PipelineExecutionError(f"UART decoding failed: {e}", step_name=step_name) from e
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"frames": frames,
|
|
124
|
+
"num_frames": len(frames),
|
|
125
|
+
"errors": errors,
|
|
126
|
+
"statistics": statistics,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@register_handler("decoder.spi")
|
|
131
|
+
def handle_decoder_spi(
|
|
132
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
133
|
+
) -> dict[str, Any]:
|
|
134
|
+
"""Decode SPI (Serial Peripheral Interface) protocol.
|
|
135
|
+
|
|
136
|
+
Inputs:
|
|
137
|
+
clk: Clock signal (DigitalTrace)
|
|
138
|
+
mosi: MOSI (Master Out Slave In) signal (DigitalTrace, optional)
|
|
139
|
+
miso: MISO (Master In Slave Out) signal (DigitalTrace, optional)
|
|
140
|
+
cs: Chip Select signal (DigitalTrace, optional)
|
|
141
|
+
|
|
142
|
+
Parameters:
|
|
143
|
+
clock_edge (str, optional): Capture edge ('rising' or 'falling') (default: 'rising')
|
|
144
|
+
bit_order (str, optional): Bit order ('msb' or 'lsb') (default: 'msb')
|
|
145
|
+
word_size (int, optional): Word size in bits (default: 8)
|
|
146
|
+
cs_active_low (bool, optional): CS is active low (default: True)
|
|
147
|
+
|
|
148
|
+
Outputs:
|
|
149
|
+
frames: List of decoded SPI frames
|
|
150
|
+
num_frames: Number of frames decoded
|
|
151
|
+
statistics: Decoding statistics dict
|
|
152
|
+
"""
|
|
153
|
+
protocols = _get_protocols()
|
|
154
|
+
|
|
155
|
+
clk = inputs.get("clk")
|
|
156
|
+
if clk is None:
|
|
157
|
+
raise PipelineExecutionError(
|
|
158
|
+
"Missing required input 'clk'. Suggestion: Provide clock signal",
|
|
159
|
+
step_name=step_name,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
mosi = inputs.get("mosi")
|
|
163
|
+
miso = inputs.get("miso")
|
|
164
|
+
cs = inputs.get("cs")
|
|
165
|
+
|
|
166
|
+
if mosi is None and miso is None:
|
|
167
|
+
raise PipelineExecutionError(
|
|
168
|
+
"At least one of 'mosi' or 'miso' required. Suggestion: Provide MOSI and/or MISO signals",
|
|
169
|
+
step_name=step_name,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
clock_edge = params.get("clock_edge", "rising")
|
|
173
|
+
bit_order = params.get("bit_order", "msb")
|
|
174
|
+
word_size = params.get("word_size", 8)
|
|
175
|
+
cs_active_low = params.get("cs_active_low", True)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
decoder = protocols.SPIDecoder(
|
|
179
|
+
clock_edge=clock_edge,
|
|
180
|
+
bit_order=bit_order,
|
|
181
|
+
word_size=word_size,
|
|
182
|
+
cs_active_low=cs_active_low,
|
|
183
|
+
)
|
|
184
|
+
frames = decoder.decode(clk, mosi=mosi, miso=miso, cs=cs)
|
|
185
|
+
|
|
186
|
+
statistics = {
|
|
187
|
+
"total_frames": len(frames),
|
|
188
|
+
"total_bytes": sum(len(f.data) if hasattr(f, "data") else 0 for f in frames),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
raise PipelineExecutionError(f"SPI decoding failed: {e}", step_name=step_name) from e
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
"frames": frames,
|
|
196
|
+
"num_frames": len(frames),
|
|
197
|
+
"statistics": statistics,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@register_handler("decoder.i2c")
|
|
202
|
+
def handle_decoder_i2c(
|
|
203
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
204
|
+
) -> dict[str, Any]:
|
|
205
|
+
"""Decode I2C (Inter-Integrated Circuit) protocol.
|
|
206
|
+
|
|
207
|
+
Inputs:
|
|
208
|
+
scl: I2C clock signal (DigitalTrace)
|
|
209
|
+
sda: I2C data signal (DigitalTrace)
|
|
210
|
+
|
|
211
|
+
Parameters:
|
|
212
|
+
address_bits (int, optional): Address width (7 or 10) (default: 7)
|
|
213
|
+
|
|
214
|
+
Outputs:
|
|
215
|
+
frames: List of decoded I2C frames
|
|
216
|
+
num_frames: Number of frames decoded
|
|
217
|
+
addresses: Set of I2C addresses seen
|
|
218
|
+
statistics: Decoding statistics dict
|
|
219
|
+
"""
|
|
220
|
+
protocols = _get_protocols()
|
|
221
|
+
|
|
222
|
+
scl = inputs.get("scl")
|
|
223
|
+
sda = inputs.get("sda")
|
|
224
|
+
|
|
225
|
+
if scl is None or sda is None:
|
|
226
|
+
raise PipelineExecutionError(
|
|
227
|
+
"Missing required inputs 'scl' and 'sda'. Suggestion: Provide both SCL and SDA signals",
|
|
228
|
+
step_name=step_name,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
address_bits = params.get("address_bits", 7)
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
decoder = protocols.I2CDecoder(address_bits=address_bits)
|
|
235
|
+
frames = decoder.decode(scl, sda)
|
|
236
|
+
|
|
237
|
+
# Extract addresses
|
|
238
|
+
addresses = set()
|
|
239
|
+
for f in frames:
|
|
240
|
+
if hasattr(f, "address"):
|
|
241
|
+
addresses.add(f.address)
|
|
242
|
+
|
|
243
|
+
statistics = {
|
|
244
|
+
"total_frames": len(frames),
|
|
245
|
+
"unique_addresses": len(addresses),
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
except Exception as e:
|
|
249
|
+
raise PipelineExecutionError(f"I2C decoding failed: {e}", step_name=step_name) from e
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"frames": frames,
|
|
253
|
+
"num_frames": len(frames),
|
|
254
|
+
"addresses": sorted(addresses),
|
|
255
|
+
"statistics": statistics,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@register_handler("decoder.can")
|
|
260
|
+
def handle_decoder_can(
|
|
261
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
262
|
+
) -> dict[str, Any]:
|
|
263
|
+
"""Decode CAN bus protocol.
|
|
264
|
+
|
|
265
|
+
Inputs:
|
|
266
|
+
trace: CAN bus signal (WaveformTrace or DigitalTrace)
|
|
267
|
+
|
|
268
|
+
Parameters:
|
|
269
|
+
bit_rate (int): CAN bit rate in bps (e.g., 125000, 250000, 500000, 1000000)
|
|
270
|
+
sample_point (float, optional): Sample point (0-1) (default: 0.75)
|
|
271
|
+
|
|
272
|
+
Outputs:
|
|
273
|
+
frames: List of decoded CAN frames
|
|
274
|
+
num_frames: Number of frames decoded
|
|
275
|
+
can_ids: Set of CAN IDs seen
|
|
276
|
+
statistics: Decoding statistics dict
|
|
277
|
+
"""
|
|
278
|
+
protocols = _get_protocols()
|
|
279
|
+
|
|
280
|
+
trace = inputs.get("trace")
|
|
281
|
+
if trace is None:
|
|
282
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
283
|
+
|
|
284
|
+
bit_rate = params.get("bit_rate")
|
|
285
|
+
if bit_rate is None:
|
|
286
|
+
raise PipelineExecutionError(
|
|
287
|
+
"Missing required parameter 'bit_rate'. Suggestion: Add 'bit_rate: 500000' to params",
|
|
288
|
+
step_name=step_name,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
sample_point = params.get("sample_point", 0.75)
|
|
292
|
+
|
|
293
|
+
try:
|
|
294
|
+
decoder = protocols.CANDecoder(bit_rate=bit_rate, sample_point=sample_point)
|
|
295
|
+
frames = decoder.decode(trace)
|
|
296
|
+
|
|
297
|
+
# Extract CAN IDs
|
|
298
|
+
can_ids = set()
|
|
299
|
+
for f in frames:
|
|
300
|
+
if hasattr(f, "can_id"):
|
|
301
|
+
can_ids.add(f.can_id)
|
|
302
|
+
|
|
303
|
+
statistics = {
|
|
304
|
+
"total_frames": len(frames),
|
|
305
|
+
"unique_ids": len(can_ids),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
raise PipelineExecutionError(f"CAN decoding failed: {e}", step_name=step_name) from e
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
"frames": frames,
|
|
313
|
+
"num_frames": len(frames),
|
|
314
|
+
"can_ids": sorted(can_ids),
|
|
315
|
+
"statistics": statistics,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@register_handler("decoder.can_fd")
|
|
320
|
+
def handle_decoder_can_fd(
|
|
321
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
322
|
+
) -> dict[str, Any]:
|
|
323
|
+
"""Decode CAN-FD protocol.
|
|
324
|
+
|
|
325
|
+
Inputs:
|
|
326
|
+
trace: CAN-FD bus signal
|
|
327
|
+
|
|
328
|
+
Parameters:
|
|
329
|
+
nominal_bit_rate (int): Nominal bit rate in bps
|
|
330
|
+
data_bit_rate (int): Data phase bit rate in bps
|
|
331
|
+
sample_point (float, optional): Sample point (0-1) (default: 0.75)
|
|
332
|
+
|
|
333
|
+
Outputs:
|
|
334
|
+
frames: List of decoded CAN-FD frames
|
|
335
|
+
num_frames: Number of frames decoded
|
|
336
|
+
statistics: Decoding statistics dict
|
|
337
|
+
"""
|
|
338
|
+
protocols = _get_protocols()
|
|
339
|
+
|
|
340
|
+
trace = inputs.get("trace")
|
|
341
|
+
if trace is None:
|
|
342
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
343
|
+
|
|
344
|
+
nominal_bit_rate = params.get("nominal_bit_rate")
|
|
345
|
+
data_bit_rate = params.get("data_bit_rate")
|
|
346
|
+
|
|
347
|
+
if nominal_bit_rate is None or data_bit_rate is None:
|
|
348
|
+
raise PipelineExecutionError(
|
|
349
|
+
"Missing required parameters 'nominal_bit_rate' and 'data_bit_rate'",
|
|
350
|
+
step_name=step_name,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
sample_point = params.get("sample_point", 0.75)
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
decoder = protocols.CANFDDecoder(
|
|
357
|
+
nominal_bit_rate=nominal_bit_rate,
|
|
358
|
+
data_bit_rate=data_bit_rate,
|
|
359
|
+
sample_point=sample_point,
|
|
360
|
+
)
|
|
361
|
+
frames = decoder.decode(trace)
|
|
362
|
+
|
|
363
|
+
statistics = {"total_frames": len(frames)}
|
|
364
|
+
|
|
365
|
+
except Exception as e:
|
|
366
|
+
raise PipelineExecutionError(f"CAN-FD decoding failed: {e}", step_name=step_name) from e
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
"frames": frames,
|
|
370
|
+
"num_frames": len(frames),
|
|
371
|
+
"statistics": statistics,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@register_handler("decoder.lin")
|
|
376
|
+
def handle_decoder_lin(
|
|
377
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
378
|
+
) -> dict[str, Any]:
|
|
379
|
+
"""Decode LIN bus protocol.
|
|
380
|
+
|
|
381
|
+
Inputs:
|
|
382
|
+
trace: LIN bus signal
|
|
383
|
+
|
|
384
|
+
Parameters:
|
|
385
|
+
baud_rate (int): LIN baud rate in bps (typically 9600 or 19200)
|
|
386
|
+
|
|
387
|
+
Outputs:
|
|
388
|
+
frames: List of decoded LIN frames
|
|
389
|
+
num_frames: Number of frames decoded
|
|
390
|
+
statistics: Decoding statistics dict
|
|
391
|
+
"""
|
|
392
|
+
protocols = _get_protocols()
|
|
393
|
+
|
|
394
|
+
trace = inputs.get("trace")
|
|
395
|
+
if trace is None:
|
|
396
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
397
|
+
|
|
398
|
+
baud_rate = params.get("baud_rate")
|
|
399
|
+
if baud_rate is None:
|
|
400
|
+
raise PipelineExecutionError("Missing required parameter 'baud_rate'", step_name=step_name)
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
decoder = protocols.LINDecoder(baud_rate=baud_rate)
|
|
404
|
+
frames = decoder.decode(trace)
|
|
405
|
+
|
|
406
|
+
statistics = {"total_frames": len(frames)}
|
|
407
|
+
|
|
408
|
+
except Exception as e:
|
|
409
|
+
raise PipelineExecutionError(f"LIN decoding failed: {e}", step_name=step_name) from e
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
"frames": frames,
|
|
413
|
+
"num_frames": len(frames),
|
|
414
|
+
"statistics": statistics,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
@register_handler("decoder.flexray")
|
|
419
|
+
def handle_decoder_flexray(
|
|
420
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
421
|
+
) -> dict[str, Any]:
|
|
422
|
+
"""Decode FlexRay protocol.
|
|
423
|
+
|
|
424
|
+
Inputs:
|
|
425
|
+
trace: FlexRay bus signal
|
|
426
|
+
|
|
427
|
+
Parameters:
|
|
428
|
+
channel (str): FlexRay channel ('A' or 'B')
|
|
429
|
+
bit_rate (int): Bit rate in bps (typically 10000000)
|
|
430
|
+
|
|
431
|
+
Outputs:
|
|
432
|
+
frames: List of decoded FlexRay frames
|
|
433
|
+
num_frames: Number of frames decoded
|
|
434
|
+
statistics: Decoding statistics dict
|
|
435
|
+
"""
|
|
436
|
+
protocols = _get_protocols()
|
|
437
|
+
|
|
438
|
+
trace = inputs.get("trace")
|
|
439
|
+
if trace is None:
|
|
440
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
441
|
+
|
|
442
|
+
channel = params.get("channel", "A")
|
|
443
|
+
bit_rate = params.get("bit_rate", 10000000)
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
decoder = protocols.FlexRayDecoder(channel=channel, bit_rate=bit_rate)
|
|
447
|
+
frames = decoder.decode(trace)
|
|
448
|
+
|
|
449
|
+
statistics = {"total_frames": len(frames)}
|
|
450
|
+
|
|
451
|
+
except Exception as e:
|
|
452
|
+
raise PipelineExecutionError(f"FlexRay decoding failed: {e}", step_name=step_name) from e
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"frames": frames,
|
|
456
|
+
"num_frames": len(frames),
|
|
457
|
+
"statistics": statistics,
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@register_handler("decoder.onewire")
|
|
462
|
+
def handle_decoder_onewire(
|
|
463
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
464
|
+
) -> dict[str, Any]:
|
|
465
|
+
"""Decode 1-Wire protocol.
|
|
466
|
+
|
|
467
|
+
Inputs:
|
|
468
|
+
trace: 1-Wire bus signal
|
|
469
|
+
|
|
470
|
+
Parameters:
|
|
471
|
+
None
|
|
472
|
+
|
|
473
|
+
Outputs:
|
|
474
|
+
frames: List of decoded 1-Wire frames
|
|
475
|
+
num_frames: Number of frames decoded
|
|
476
|
+
statistics: Decoding statistics dict
|
|
477
|
+
"""
|
|
478
|
+
protocols = _get_protocols()
|
|
479
|
+
|
|
480
|
+
trace = inputs.get("trace")
|
|
481
|
+
if trace is None:
|
|
482
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
decoder = protocols.OneWireDecoder()
|
|
486
|
+
frames = decoder.decode(trace)
|
|
487
|
+
|
|
488
|
+
statistics = {"total_frames": len(frames)}
|
|
489
|
+
|
|
490
|
+
except Exception as e:
|
|
491
|
+
raise PipelineExecutionError(f"1-Wire decoding failed: {e}", step_name=step_name) from e
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
"frames": frames,
|
|
495
|
+
"num_frames": len(frames),
|
|
496
|
+
"statistics": statistics,
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
@register_handler("decoder.manchester")
|
|
501
|
+
def handle_decoder_manchester(
|
|
502
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
503
|
+
) -> dict[str, Any]:
|
|
504
|
+
"""Decode Manchester encoded data.
|
|
505
|
+
|
|
506
|
+
Inputs:
|
|
507
|
+
trace: Manchester encoded signal
|
|
508
|
+
|
|
509
|
+
Parameters:
|
|
510
|
+
bit_rate (int): Bit rate in bps
|
|
511
|
+
ieee_convention (bool, optional): Use IEEE 802.3 convention (default: True)
|
|
512
|
+
|
|
513
|
+
Outputs:
|
|
514
|
+
frames: List of decoded frames
|
|
515
|
+
num_frames: Number of frames decoded
|
|
516
|
+
data: Decoded bit stream
|
|
517
|
+
statistics: Decoding statistics dict
|
|
518
|
+
"""
|
|
519
|
+
protocols = _get_protocols()
|
|
520
|
+
|
|
521
|
+
trace = inputs.get("trace")
|
|
522
|
+
if trace is None:
|
|
523
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
524
|
+
|
|
525
|
+
bit_rate = params.get("bit_rate")
|
|
526
|
+
if bit_rate is None:
|
|
527
|
+
raise PipelineExecutionError("Missing required parameter 'bit_rate'", step_name=step_name)
|
|
528
|
+
|
|
529
|
+
ieee_convention = params.get("ieee_convention", True)
|
|
530
|
+
|
|
531
|
+
try:
|
|
532
|
+
decoder = protocols.ManchesterDecoder(bit_rate=bit_rate, ieee_convention=ieee_convention)
|
|
533
|
+
frames = decoder.decode(trace)
|
|
534
|
+
data = b"".join(f.data if hasattr(f, "data") else b"" for f in frames)
|
|
535
|
+
|
|
536
|
+
statistics = {
|
|
537
|
+
"total_frames": len(frames),
|
|
538
|
+
"total_bits": len(data) * 8,
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
except Exception as e:
|
|
542
|
+
raise PipelineExecutionError(f"Manchester decoding failed: {e}", step_name=step_name) from e
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
"frames": frames,
|
|
546
|
+
"num_frames": len(frames),
|
|
547
|
+
"data": data,
|
|
548
|
+
"statistics": statistics,
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@register_handler("decoder.i2s")
|
|
553
|
+
def handle_decoder_i2s(
|
|
554
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
555
|
+
) -> dict[str, Any]:
|
|
556
|
+
"""Decode I2S (Inter-IC Sound) audio protocol.
|
|
557
|
+
|
|
558
|
+
Inputs:
|
|
559
|
+
sck: Serial clock (bit clock)
|
|
560
|
+
ws: Word select (frame clock)
|
|
561
|
+
sd: Serial data
|
|
562
|
+
|
|
563
|
+
Parameters:
|
|
564
|
+
bits_per_sample (int, optional): Bits per sample (default: 16)
|
|
565
|
+
|
|
566
|
+
Outputs:
|
|
567
|
+
frames: List of decoded audio frames
|
|
568
|
+
num_frames: Number of frames decoded
|
|
569
|
+
statistics: Decoding statistics dict
|
|
570
|
+
"""
|
|
571
|
+
protocols = _get_protocols()
|
|
572
|
+
|
|
573
|
+
sck = inputs.get("sck")
|
|
574
|
+
ws = inputs.get("ws")
|
|
575
|
+
sd = inputs.get("sd")
|
|
576
|
+
|
|
577
|
+
if sck is None or ws is None or sd is None:
|
|
578
|
+
raise PipelineExecutionError(
|
|
579
|
+
"Missing required inputs 'sck', 'ws', 'sd'", step_name=step_name
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
bits_per_sample = params.get("bits_per_sample", 16)
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
decoder = protocols.I2SDecoder(bits_per_sample=bits_per_sample)
|
|
586
|
+
frames = decoder.decode(sck, ws, sd)
|
|
587
|
+
|
|
588
|
+
statistics = {"total_frames": len(frames)}
|
|
589
|
+
|
|
590
|
+
except Exception as e:
|
|
591
|
+
raise PipelineExecutionError(f"I2S decoding failed: {e}", step_name=step_name) from e
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
"frames": frames,
|
|
595
|
+
"num_frames": len(frames),
|
|
596
|
+
"statistics": statistics,
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
@register_handler("decoder.jtag")
|
|
601
|
+
def handle_decoder_jtag(
|
|
602
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
603
|
+
) -> dict[str, Any]:
|
|
604
|
+
"""Decode JTAG debug interface.
|
|
605
|
+
|
|
606
|
+
Inputs:
|
|
607
|
+
tck: Test clock
|
|
608
|
+
tms: Test mode select
|
|
609
|
+
tdi: Test data in
|
|
610
|
+
tdo: Test data out (optional)
|
|
611
|
+
|
|
612
|
+
Parameters:
|
|
613
|
+
None
|
|
614
|
+
|
|
615
|
+
Outputs:
|
|
616
|
+
frames: List of decoded JTAG frames
|
|
617
|
+
num_frames: Number of frames decoded
|
|
618
|
+
statistics: Decoding statistics dict
|
|
619
|
+
"""
|
|
620
|
+
protocols = _get_protocols()
|
|
621
|
+
|
|
622
|
+
tck = inputs.get("tck")
|
|
623
|
+
tms = inputs.get("tms")
|
|
624
|
+
tdi = inputs.get("tdi")
|
|
625
|
+
tdo = inputs.get("tdo")
|
|
626
|
+
|
|
627
|
+
if tck is None or tms is None or tdi is None:
|
|
628
|
+
raise PipelineExecutionError(
|
|
629
|
+
"Missing required inputs 'tck', 'tms', 'tdi'", step_name=step_name
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
decoder = protocols.JTAGDecoder()
|
|
634
|
+
frames = decoder.decode(tck, tms, tdi, tdo)
|
|
635
|
+
|
|
636
|
+
statistics = {"total_frames": len(frames)}
|
|
637
|
+
|
|
638
|
+
except Exception as e:
|
|
639
|
+
raise PipelineExecutionError(f"JTAG decoding failed: {e}", step_name=step_name) from e
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
"frames": frames,
|
|
643
|
+
"num_frames": len(frames),
|
|
644
|
+
"statistics": statistics,
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
@register_handler("decoder.swd")
|
|
649
|
+
def handle_decoder_swd(
|
|
650
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
651
|
+
) -> dict[str, Any]:
|
|
652
|
+
"""Decode SWD (Serial Wire Debug) protocol.
|
|
653
|
+
|
|
654
|
+
Inputs:
|
|
655
|
+
swclk: Serial wire clock
|
|
656
|
+
swdio: Serial wire data I/O
|
|
657
|
+
|
|
658
|
+
Parameters:
|
|
659
|
+
None
|
|
660
|
+
|
|
661
|
+
Outputs:
|
|
662
|
+
frames: List of decoded SWD frames
|
|
663
|
+
num_frames: Number of frames decoded
|
|
664
|
+
statistics: Decoding statistics dict
|
|
665
|
+
"""
|
|
666
|
+
protocols = _get_protocols()
|
|
667
|
+
|
|
668
|
+
swclk = inputs.get("swclk")
|
|
669
|
+
swdio = inputs.get("swdio")
|
|
670
|
+
|
|
671
|
+
if swclk is None or swdio is None:
|
|
672
|
+
raise PipelineExecutionError(
|
|
673
|
+
"Missing required inputs 'swclk' and 'swdio'", step_name=step_name
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
try:
|
|
677
|
+
decoder = protocols.SWDDecoder()
|
|
678
|
+
frames = decoder.decode(swclk, swdio)
|
|
679
|
+
|
|
680
|
+
statistics = {"total_frames": len(frames)}
|
|
681
|
+
|
|
682
|
+
except Exception as e:
|
|
683
|
+
raise PipelineExecutionError(f"SWD decoding failed: {e}", step_name=step_name) from e
|
|
684
|
+
|
|
685
|
+
return {
|
|
686
|
+
"frames": frames,
|
|
687
|
+
"num_frames": len(frames),
|
|
688
|
+
"statistics": statistics,
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
@register_handler("decoder.usb")
|
|
693
|
+
def handle_decoder_usb(
|
|
694
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
695
|
+
) -> dict[str, Any]:
|
|
696
|
+
"""Decode USB protocol.
|
|
697
|
+
|
|
698
|
+
Inputs:
|
|
699
|
+
dp: USB D+ signal
|
|
700
|
+
dm: USB D- signal
|
|
701
|
+
|
|
702
|
+
Parameters:
|
|
703
|
+
speed (str, optional): USB speed ('low', 'full', 'high') (default: 'full')
|
|
704
|
+
|
|
705
|
+
Outputs:
|
|
706
|
+
frames: List of decoded USB packets
|
|
707
|
+
num_frames: Number of packets decoded
|
|
708
|
+
statistics: Decoding statistics dict
|
|
709
|
+
"""
|
|
710
|
+
protocols = _get_protocols()
|
|
711
|
+
|
|
712
|
+
dp = inputs.get("dp")
|
|
713
|
+
dm = inputs.get("dm")
|
|
714
|
+
|
|
715
|
+
if dp is None or dm is None:
|
|
716
|
+
raise PipelineExecutionError("Missing required inputs 'dp' and 'dm'", step_name=step_name)
|
|
717
|
+
|
|
718
|
+
speed = params.get("speed", "full")
|
|
719
|
+
|
|
720
|
+
try:
|
|
721
|
+
decoder = protocols.USBDecoder(speed=speed)
|
|
722
|
+
frames = decoder.decode(dp, dm)
|
|
723
|
+
|
|
724
|
+
statistics = {"total_frames": len(frames)}
|
|
725
|
+
|
|
726
|
+
except Exception as e:
|
|
727
|
+
raise PipelineExecutionError(f"USB decoding failed: {e}", step_name=step_name) from e
|
|
728
|
+
|
|
729
|
+
return {
|
|
730
|
+
"frames": frames,
|
|
731
|
+
"num_frames": len(frames),
|
|
732
|
+
"statistics": statistics,
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
@register_handler("decoder.hdlc")
|
|
737
|
+
def handle_decoder_hdlc(
|
|
738
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
739
|
+
) -> dict[str, Any]:
|
|
740
|
+
"""Decode HDLC frames.
|
|
741
|
+
|
|
742
|
+
Inputs:
|
|
743
|
+
trace: HDLC signal
|
|
744
|
+
|
|
745
|
+
Parameters:
|
|
746
|
+
None
|
|
747
|
+
|
|
748
|
+
Outputs:
|
|
749
|
+
frames: List of decoded HDLC frames
|
|
750
|
+
num_frames: Number of frames decoded
|
|
751
|
+
statistics: Decoding statistics dict
|
|
752
|
+
"""
|
|
753
|
+
protocols = _get_protocols()
|
|
754
|
+
|
|
755
|
+
trace = inputs.get("trace")
|
|
756
|
+
if trace is None:
|
|
757
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
758
|
+
|
|
759
|
+
try:
|
|
760
|
+
decoder = protocols.HDLCDecoder()
|
|
761
|
+
frames = decoder.decode(trace)
|
|
762
|
+
|
|
763
|
+
statistics = {"total_frames": len(frames)}
|
|
764
|
+
|
|
765
|
+
except Exception as e:
|
|
766
|
+
raise PipelineExecutionError(f"HDLC decoding failed: {e}", step_name=step_name) from e
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
"frames": frames,
|
|
770
|
+
"num_frames": len(frames),
|
|
771
|
+
"statistics": statistics,
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
@register_handler("decoder.auto")
|
|
776
|
+
def handle_decoder_auto(
|
|
777
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
778
|
+
) -> dict[str, Any]:
|
|
779
|
+
"""Auto-detect and decode protocol.
|
|
780
|
+
|
|
781
|
+
Inputs:
|
|
782
|
+
trace: WaveformTrace or DigitalTrace
|
|
783
|
+
|
|
784
|
+
Parameters:
|
|
785
|
+
protocol (str, optional): Force specific protocol (None for auto-detect)
|
|
786
|
+
min_confidence (float, optional): Minimum detection confidence (0-1) (default: 0.5)
|
|
787
|
+
|
|
788
|
+
Outputs:
|
|
789
|
+
protocol: Detected protocol name
|
|
790
|
+
frames: List of decoded frames
|
|
791
|
+
confidence: Detection confidence (0-1)
|
|
792
|
+
baud_rate: Detected baud/bit rate (if applicable)
|
|
793
|
+
config: Protocol configuration parameters
|
|
794
|
+
errors: List of decoding errors
|
|
795
|
+
statistics: Decoding statistics
|
|
796
|
+
"""
|
|
797
|
+
convenience = _get_convenience()
|
|
798
|
+
|
|
799
|
+
trace = inputs.get("trace")
|
|
800
|
+
if trace is None:
|
|
801
|
+
raise PipelineExecutionError("Missing required input 'trace'", step_name=step_name)
|
|
802
|
+
|
|
803
|
+
protocol = params.get("protocol")
|
|
804
|
+
min_confidence = params.get("min_confidence", 0.5)
|
|
805
|
+
|
|
806
|
+
try:
|
|
807
|
+
result = convenience.auto_decode(trace, protocol=protocol, min_confidence=min_confidence)
|
|
808
|
+
|
|
809
|
+
# Extract all fields from DecodeResult
|
|
810
|
+
return {
|
|
811
|
+
"protocol": result.protocol,
|
|
812
|
+
"frames": result.frames,
|
|
813
|
+
"confidence": result.confidence,
|
|
814
|
+
"baud_rate": result.baud_rate,
|
|
815
|
+
"config": result.config,
|
|
816
|
+
"errors": result.errors,
|
|
817
|
+
"statistics": result.statistics,
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
except Exception as e:
|
|
821
|
+
raise PipelineExecutionError(f"Auto-decode failed: {e}", step_name=step_name) from e
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
@register_handler("decoder.multi_protocol")
|
|
825
|
+
def handle_decoder_multi_protocol(
|
|
826
|
+
inputs: dict[str, Any], params: dict[str, Any], step_name: str
|
|
827
|
+
) -> dict[str, Any]:
|
|
828
|
+
"""Decode multiple protocols from multi-channel trace.
|
|
829
|
+
|
|
830
|
+
Inputs:
|
|
831
|
+
channels: Dict mapping channel names to traces
|
|
832
|
+
|
|
833
|
+
Parameters:
|
|
834
|
+
protocols (dict): Dict mapping protocol names to decoder configs
|
|
835
|
+
Example: {"uart": {"channel": "CH1", "baud_rate": 115200},
|
|
836
|
+
"spi": {"clk": "CH2", "mosi": "CH3"}}
|
|
837
|
+
|
|
838
|
+
Outputs:
|
|
839
|
+
results: Dict mapping protocol names to decode results
|
|
840
|
+
summary: Summary statistics across all protocols
|
|
841
|
+
"""
|
|
842
|
+
# This is a meta-handler that dispatches to other handlers
|
|
843
|
+
channels = inputs.get("channels")
|
|
844
|
+
if channels is None:
|
|
845
|
+
raise PipelineExecutionError("Missing required input 'channels'", step_name=step_name)
|
|
846
|
+
|
|
847
|
+
protocol_configs = params.get("protocols")
|
|
848
|
+
if not protocol_configs:
|
|
849
|
+
raise PipelineExecutionError(
|
|
850
|
+
"Missing required parameter 'protocols'. Suggestion: Specify protocols dict with decoder configs",
|
|
851
|
+
step_name=step_name,
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
results = {}
|
|
855
|
+
total_frames = 0
|
|
856
|
+
|
|
857
|
+
for proto_name, proto_config in protocol_configs.items():
|
|
858
|
+
try:
|
|
859
|
+
# Get handler for this protocol
|
|
860
|
+
from oscura.pipeline.handlers import get_handler
|
|
861
|
+
|
|
862
|
+
handler_type = f"decoder.{proto_name}"
|
|
863
|
+
handler = get_handler(handler_type)
|
|
864
|
+
|
|
865
|
+
if handler is None:
|
|
866
|
+
raise PipelineExecutionError(
|
|
867
|
+
f"Unknown protocol: {proto_name}. Suggestion: Supported protocols: uart, spi, i2c, can, etc.",
|
|
868
|
+
step_name=step_name,
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
# Map channel names to actual traces
|
|
872
|
+
channel_name = proto_config.get("channel")
|
|
873
|
+
if channel_name:
|
|
874
|
+
proto_inputs = {"trace": channels.get(channel_name)}
|
|
875
|
+
else:
|
|
876
|
+
# Multi-signal protocols (SPI, I2C, etc.)
|
|
877
|
+
proto_inputs = {}
|
|
878
|
+
for key in ["clk", "mosi", "miso", "cs", "scl", "sda", "dp", "dm"]:
|
|
879
|
+
if key in proto_config:
|
|
880
|
+
ch_name = proto_config[key]
|
|
881
|
+
proto_inputs[key] = channels.get(ch_name)
|
|
882
|
+
|
|
883
|
+
# Decode this protocol
|
|
884
|
+
result = handler(proto_inputs, proto_config, step_name)
|
|
885
|
+
results[proto_name] = result
|
|
886
|
+
total_frames += result.get("num_frames", 0)
|
|
887
|
+
|
|
888
|
+
except Exception as e:
|
|
889
|
+
results[proto_name] = {"error": str(e)}
|
|
890
|
+
|
|
891
|
+
summary = {
|
|
892
|
+
"protocols_decoded": len([r for r in results.values() if "error" not in r]),
|
|
893
|
+
"total_frames": total_frames,
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return {
|
|
897
|
+
"results": results,
|
|
898
|
+
"summary": summary,
|
|
899
|
+
}
|