oscura 0.0.1__py3-none-any.whl → 0.1.1__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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
oscura/cli/decode.py
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
"""Oscura Decode Command implementing CLI-003.
|
|
2
|
+
|
|
3
|
+
Provides CLI for protocol decoding with automatic protocol detection and
|
|
4
|
+
error highlighting.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
$ oscura decode serial_capture.wfm
|
|
9
|
+
$ oscura decode i2c_bus.wfm --protocol I2C
|
|
10
|
+
$ oscura decode uart.wfm --protocol UART --baud-rate 115200
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import click
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from oscura.cli.main import format_output
|
|
23
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket, WaveformTrace
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("oscura.cli.decode")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@click.command() # type: ignore[misc]
|
|
29
|
+
@click.argument("file", type=click.Path(exists=True)) # type: ignore[misc]
|
|
30
|
+
@click.option( # type: ignore[misc]
|
|
31
|
+
"--protocol",
|
|
32
|
+
type=click.Choice(["uart", "spi", "i2c", "can", "auto"], case_sensitive=False),
|
|
33
|
+
default="auto",
|
|
34
|
+
help="Protocol type (default: auto-detect).",
|
|
35
|
+
)
|
|
36
|
+
@click.option( # type: ignore[misc]
|
|
37
|
+
"--baud-rate",
|
|
38
|
+
type=int,
|
|
39
|
+
default=None,
|
|
40
|
+
help="Baud rate for UART (auto-detect if not specified).",
|
|
41
|
+
)
|
|
42
|
+
@click.option( # type: ignore[misc]
|
|
43
|
+
"--parity",
|
|
44
|
+
type=click.Choice(["none", "even", "odd"], case_sensitive=False),
|
|
45
|
+
default="none",
|
|
46
|
+
help="Parity for UART (default: none).",
|
|
47
|
+
)
|
|
48
|
+
@click.option( # type: ignore[misc]
|
|
49
|
+
"--stop-bits",
|
|
50
|
+
type=click.Choice(["1", "2"]),
|
|
51
|
+
default="1",
|
|
52
|
+
help="Stop bits for UART (default: 1).",
|
|
53
|
+
)
|
|
54
|
+
@click.option( # type: ignore[misc]
|
|
55
|
+
"--show-errors",
|
|
56
|
+
is_flag=True,
|
|
57
|
+
help="Show only errors with context.",
|
|
58
|
+
)
|
|
59
|
+
@click.option( # type: ignore[misc]
|
|
60
|
+
"--output",
|
|
61
|
+
type=click.Choice(["json", "csv", "html", "table"], case_sensitive=False),
|
|
62
|
+
default="table",
|
|
63
|
+
help="Output format (default: table).",
|
|
64
|
+
)
|
|
65
|
+
@click.pass_context # type: ignore[misc]
|
|
66
|
+
def decode(
|
|
67
|
+
ctx: click.Context,
|
|
68
|
+
file: str,
|
|
69
|
+
protocol: str,
|
|
70
|
+
baud_rate: int | None,
|
|
71
|
+
parity: str,
|
|
72
|
+
stop_bits: str,
|
|
73
|
+
show_errors: bool,
|
|
74
|
+
output: str,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Decode serial protocol data.
|
|
77
|
+
|
|
78
|
+
Automatically detects and decodes common serial protocols (UART, SPI, I2C, CAN).
|
|
79
|
+
Can highlight errors with surrounding context for debugging.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
ctx: Click context object.
|
|
83
|
+
file: Path to waveform file to decode.
|
|
84
|
+
protocol: Protocol type (uart, spi, i2c, can, auto).
|
|
85
|
+
baud_rate: Baud rate for UART (None for auto-detect).
|
|
86
|
+
parity: Parity setting for UART (none, even, odd).
|
|
87
|
+
stop_bits: Number of stop bits for UART (1 or 2).
|
|
88
|
+
show_errors: Show only packets with errors.
|
|
89
|
+
output: Output format (json, csv, html, table).
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
Exception: If decoding fails or file cannot be loaded.
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
|
|
96
|
+
\b
|
|
97
|
+
# Auto-detect and decode protocol
|
|
98
|
+
$ oscura decode serial_capture.wfm
|
|
99
|
+
|
|
100
|
+
\b
|
|
101
|
+
# Decode specific protocol with parameters
|
|
102
|
+
$ oscura decode uart.wfm \\
|
|
103
|
+
--protocol UART \\
|
|
104
|
+
--baud-rate 9600 \\
|
|
105
|
+
--parity even \\
|
|
106
|
+
--stop-bits 2
|
|
107
|
+
|
|
108
|
+
\b
|
|
109
|
+
# Show only errors for debugging
|
|
110
|
+
$ oscura decode problematic.wfm --show-errors
|
|
111
|
+
|
|
112
|
+
\b
|
|
113
|
+
# Generate JSON output
|
|
114
|
+
$ oscura decode i2c.wfm --protocol I2C --output json
|
|
115
|
+
"""
|
|
116
|
+
verbose = ctx.obj.get("verbose", 0)
|
|
117
|
+
|
|
118
|
+
if verbose:
|
|
119
|
+
logger.info(f"Decoding: {file}")
|
|
120
|
+
logger.info(f"Protocol: {protocol}")
|
|
121
|
+
if protocol.lower() == "uart" and baud_rate:
|
|
122
|
+
logger.info(f"Baud rate: {baud_rate}")
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
# Import here to avoid circular imports
|
|
126
|
+
from oscura.loaders import load
|
|
127
|
+
|
|
128
|
+
# Load the trace
|
|
129
|
+
logger.debug(f"Loading trace from {file}")
|
|
130
|
+
trace = load(file)
|
|
131
|
+
|
|
132
|
+
# Perform protocol decoding
|
|
133
|
+
results = _perform_decoding(
|
|
134
|
+
trace=trace, # type: ignore[arg-type]
|
|
135
|
+
protocol=protocol,
|
|
136
|
+
baud_rate=baud_rate,
|
|
137
|
+
parity=parity,
|
|
138
|
+
stop_bits=int(stop_bits),
|
|
139
|
+
show_errors=show_errors,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Add metadata
|
|
143
|
+
results["file"] = str(Path(file).name)
|
|
144
|
+
|
|
145
|
+
# Output results
|
|
146
|
+
formatted = format_output(results, output)
|
|
147
|
+
click.echo(formatted)
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.error(f"Decoding failed: {e}")
|
|
151
|
+
if verbose > 1:
|
|
152
|
+
raise
|
|
153
|
+
click.echo(f"Error: {e}", err=True)
|
|
154
|
+
ctx.exit(1)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _to_digital(trace: WaveformTrace | DigitalTrace) -> DigitalTrace:
|
|
158
|
+
"""Convert waveform trace to digital trace.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
trace: Input trace (waveform or digital).
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Digital trace with boolean data.
|
|
165
|
+
"""
|
|
166
|
+
if isinstance(trace, DigitalTrace):
|
|
167
|
+
return trace
|
|
168
|
+
|
|
169
|
+
# Use midpoint threshold for digitization
|
|
170
|
+
data = trace.data
|
|
171
|
+
threshold = (np.max(data) + np.min(data)) / 2
|
|
172
|
+
digital_data = data > threshold
|
|
173
|
+
|
|
174
|
+
return DigitalTrace(
|
|
175
|
+
data=digital_data,
|
|
176
|
+
metadata=trace.metadata,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _perform_decoding(
|
|
181
|
+
trace: WaveformTrace | DigitalTrace,
|
|
182
|
+
protocol: str,
|
|
183
|
+
baud_rate: int | None,
|
|
184
|
+
parity: str,
|
|
185
|
+
stop_bits: int,
|
|
186
|
+
show_errors: bool,
|
|
187
|
+
) -> dict[str, Any]:
|
|
188
|
+
"""Perform protocol decoding using actual decoders.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
trace: Trace to decode.
|
|
192
|
+
protocol: Protocol type or 'auto'.
|
|
193
|
+
baud_rate: Optional baud rate for UART.
|
|
194
|
+
parity: Parity setting for UART.
|
|
195
|
+
stop_bits: Stop bits for UART.
|
|
196
|
+
show_errors: Whether to show only errors.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Dictionary of decoding results.
|
|
200
|
+
"""
|
|
201
|
+
# Import protocol decoders
|
|
202
|
+
from oscura.inference.protocol import detect_protocol
|
|
203
|
+
|
|
204
|
+
sample_rate = trace.metadata.sample_rate
|
|
205
|
+
duration_ms = len(trace.data) / sample_rate * 1e3
|
|
206
|
+
|
|
207
|
+
results: dict[str, Any] = {
|
|
208
|
+
"sample_rate": f"{sample_rate / 1e6:.1f} MHz",
|
|
209
|
+
"samples": len(trace.data),
|
|
210
|
+
"duration": f"{duration_ms:.3f} ms",
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# Auto-detect protocol if requested
|
|
214
|
+
detected_protocol = protocol
|
|
215
|
+
detection_confidence = 1.0
|
|
216
|
+
|
|
217
|
+
if protocol.lower() == "auto":
|
|
218
|
+
try:
|
|
219
|
+
detection = detect_protocol(trace, min_confidence=0.5, return_candidates=True) # type: ignore[arg-type]
|
|
220
|
+
detected_protocol = detection["protocol"].lower()
|
|
221
|
+
detection_confidence = detection["confidence"]
|
|
222
|
+
results["auto_detection"] = {
|
|
223
|
+
"protocol": detection["protocol"],
|
|
224
|
+
"confidence": f"{detection_confidence:.1%}",
|
|
225
|
+
"candidates": [
|
|
226
|
+
{"protocol": c["protocol"], "confidence": f"{c['confidence']:.1%}"}
|
|
227
|
+
for c in detection.get("candidates", [])[:3]
|
|
228
|
+
],
|
|
229
|
+
}
|
|
230
|
+
# Extract config suggestions
|
|
231
|
+
if "config" in detection:
|
|
232
|
+
if detected_protocol == "uart" and baud_rate is None:
|
|
233
|
+
baud_rate = detection["config"].get("baud_rate")
|
|
234
|
+
except Exception as e:
|
|
235
|
+
logger.warning(f"Auto-detection failed: {e}, defaulting to UART")
|
|
236
|
+
detected_protocol = "uart"
|
|
237
|
+
detection_confidence = 0.0
|
|
238
|
+
|
|
239
|
+
results["protocol"] = detected_protocol.upper()
|
|
240
|
+
|
|
241
|
+
# Convert to digital trace for decoding
|
|
242
|
+
digital_trace = _to_digital(trace)
|
|
243
|
+
|
|
244
|
+
# Decode based on protocol
|
|
245
|
+
packets: list[ProtocolPacket] = []
|
|
246
|
+
errors: list[dict[str, Any]] = []
|
|
247
|
+
|
|
248
|
+
if detected_protocol == "uart":
|
|
249
|
+
packets, errors, protocol_info = _decode_uart(
|
|
250
|
+
digital_trace, baud_rate, parity, stop_bits, show_errors
|
|
251
|
+
)
|
|
252
|
+
results.update(protocol_info)
|
|
253
|
+
|
|
254
|
+
elif detected_protocol == "spi":
|
|
255
|
+
packets, errors, protocol_info = _decode_spi(digital_trace, show_errors)
|
|
256
|
+
results.update(protocol_info)
|
|
257
|
+
|
|
258
|
+
elif detected_protocol == "i2c":
|
|
259
|
+
packets, errors, protocol_info = _decode_i2c(digital_trace, show_errors)
|
|
260
|
+
results.update(protocol_info)
|
|
261
|
+
|
|
262
|
+
elif detected_protocol == "can":
|
|
263
|
+
packets, errors, protocol_info = _decode_can(digital_trace, baud_rate, show_errors)
|
|
264
|
+
results.update(protocol_info)
|
|
265
|
+
|
|
266
|
+
# Filter to errors only if requested
|
|
267
|
+
if show_errors:
|
|
268
|
+
packets = [p for p in packets if p.errors]
|
|
269
|
+
|
|
270
|
+
# Summarize results
|
|
271
|
+
results["packets_decoded"] = len(packets)
|
|
272
|
+
results["errors_found"] = len(errors)
|
|
273
|
+
|
|
274
|
+
# Add packet details
|
|
275
|
+
results["packets"] = [
|
|
276
|
+
{
|
|
277
|
+
"index": i,
|
|
278
|
+
"timestamp": f"{p.timestamp * 1e3:.3f} ms",
|
|
279
|
+
"data": p.data.hex() if p.data else "",
|
|
280
|
+
"errors": p.errors,
|
|
281
|
+
**{k: v for k, v in (p.annotations or {}).items() if k != "data_bits"},
|
|
282
|
+
}
|
|
283
|
+
for i, p in enumerate(packets[:100]) # Limit to first 100 packets
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
if len(packets) > 100:
|
|
287
|
+
results["note"] = f"Showing first 100 of {len(packets)} packets"
|
|
288
|
+
|
|
289
|
+
# Add error details if any
|
|
290
|
+
if errors:
|
|
291
|
+
results["error_details"] = errors[:20] # Limit to first 20 errors
|
|
292
|
+
|
|
293
|
+
return results
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _decode_uart(
|
|
297
|
+
trace: DigitalTrace,
|
|
298
|
+
baud_rate: int | None,
|
|
299
|
+
parity: str,
|
|
300
|
+
stop_bits: int,
|
|
301
|
+
show_errors: bool,
|
|
302
|
+
) -> tuple[list[ProtocolPacket], list[dict[str, Any]], dict[str, Any]]:
|
|
303
|
+
"""Decode UART protocol.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
trace: Digital trace to decode.
|
|
307
|
+
baud_rate: Baud rate (None for auto-detect).
|
|
308
|
+
parity: Parity mode.
|
|
309
|
+
stop_bits: Number of stop bits.
|
|
310
|
+
show_errors: Whether to filter to errors only.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Tuple of (packets, errors, protocol_info).
|
|
314
|
+
"""
|
|
315
|
+
from oscura.analyzers.protocols.uart import UARTDecoder
|
|
316
|
+
|
|
317
|
+
# Create decoder with parameters
|
|
318
|
+
decoder = UARTDecoder(
|
|
319
|
+
baudrate=baud_rate or 0, # 0 triggers auto-detection
|
|
320
|
+
data_bits=8,
|
|
321
|
+
parity=parity, # type: ignore[arg-type]
|
|
322
|
+
stop_bits=stop_bits,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
packets = list(decoder.decode(trace))
|
|
326
|
+
errors = []
|
|
327
|
+
|
|
328
|
+
# Extract errors from packets
|
|
329
|
+
for i, pkt in enumerate(packets):
|
|
330
|
+
if pkt.errors:
|
|
331
|
+
for err in pkt.errors:
|
|
332
|
+
errors.append(
|
|
333
|
+
{
|
|
334
|
+
"packet_index": i,
|
|
335
|
+
"timestamp": f"{pkt.timestamp * 1e3:.3f} ms",
|
|
336
|
+
"type": err,
|
|
337
|
+
"data": pkt.data.hex() if pkt.data else "",
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# Determine actual baud rate used
|
|
342
|
+
actual_baud = decoder._baudrate if hasattr(decoder, "_baudrate") else baud_rate
|
|
343
|
+
|
|
344
|
+
protocol_info = {
|
|
345
|
+
"baud_rate": actual_baud,
|
|
346
|
+
"parity": parity,
|
|
347
|
+
"stop_bits": stop_bits,
|
|
348
|
+
"data_bits": 8,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return packets, errors, protocol_info
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _decode_spi(
|
|
355
|
+
trace: DigitalTrace,
|
|
356
|
+
show_errors: bool,
|
|
357
|
+
) -> tuple[list[ProtocolPacket], list[dict[str, Any]], dict[str, Any]]:
|
|
358
|
+
"""Decode SPI protocol.
|
|
359
|
+
|
|
360
|
+
Note: SPI requires multiple signals (CLK, MOSI, optionally MISO, CS).
|
|
361
|
+
This function assumes the trace contains clock data and will attempt
|
|
362
|
+
to decode what's available.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
trace: Digital trace to decode (assumed to be CLK or combined).
|
|
366
|
+
show_errors: Whether to filter to errors only.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Tuple of (packets, errors, protocol_info).
|
|
370
|
+
"""
|
|
371
|
+
from oscura.analyzers.protocols.spi import SPIDecoder
|
|
372
|
+
|
|
373
|
+
# For single-channel decode, we can only analyze timing
|
|
374
|
+
# Full SPI decode requires separate CLK, MOSI, MISO channels
|
|
375
|
+
decoder = SPIDecoder(cpol=0, cpha=0, word_size=8)
|
|
376
|
+
|
|
377
|
+
# Create MOSI from the trace data (treating as data line)
|
|
378
|
+
clk = trace.data
|
|
379
|
+
mosi = trace.data # Same data for single-channel analysis
|
|
380
|
+
|
|
381
|
+
packets = list(decoder.decode(clk=clk, mosi=mosi, sample_rate=trace.metadata.sample_rate))
|
|
382
|
+
errors = []
|
|
383
|
+
|
|
384
|
+
for i, pkt in enumerate(packets):
|
|
385
|
+
if pkt.errors:
|
|
386
|
+
for err in pkt.errors:
|
|
387
|
+
errors.append(
|
|
388
|
+
{
|
|
389
|
+
"packet_index": i,
|
|
390
|
+
"timestamp": f"{pkt.timestamp * 1e3:.3f} ms",
|
|
391
|
+
"type": err,
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Estimate clock frequency from edge timing
|
|
396
|
+
edges = np.where(np.diff(clk.astype(int)) != 0)[0]
|
|
397
|
+
if len(edges) > 1:
|
|
398
|
+
avg_period = np.mean(np.diff(edges)) / trace.metadata.sample_rate
|
|
399
|
+
clock_freq = 1 / (2 * avg_period) if avg_period > 0 else 0
|
|
400
|
+
else:
|
|
401
|
+
clock_freq = 0
|
|
402
|
+
|
|
403
|
+
protocol_info = {
|
|
404
|
+
"clock_frequency": f"{clock_freq / 1e6:.2f} MHz" if clock_freq > 0 else "Unknown",
|
|
405
|
+
"mode": "0 (CPOL=0, CPHA=0)",
|
|
406
|
+
"word_size": 8,
|
|
407
|
+
"note": "Single-channel decode. For full SPI decode, provide separate CLK/MOSI/MISO signals.",
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return packets, errors, protocol_info
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _decode_i2c(
|
|
414
|
+
trace: DigitalTrace,
|
|
415
|
+
show_errors: bool,
|
|
416
|
+
) -> tuple[list[ProtocolPacket], list[dict[str, Any]], dict[str, Any]]:
|
|
417
|
+
"""Decode I2C protocol.
|
|
418
|
+
|
|
419
|
+
Note: I2C requires two signals (SCL, SDA). This function assumes
|
|
420
|
+
the trace is SDA and attempts to detect SCL from timing patterns.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
trace: Digital trace to decode.
|
|
424
|
+
show_errors: Whether to filter to errors only.
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
Tuple of (packets, errors, protocol_info).
|
|
428
|
+
"""
|
|
429
|
+
from oscura.analyzers.protocols.i2c import I2CDecoder
|
|
430
|
+
|
|
431
|
+
# For single-channel, assume it's SDA and create synthetic SCL from edges
|
|
432
|
+
sda = trace.data
|
|
433
|
+
sample_rate = trace.metadata.sample_rate
|
|
434
|
+
|
|
435
|
+
# Try to find clock pattern from edge timing
|
|
436
|
+
edges = np.where(np.diff(sda.astype(int)) != 0)[0]
|
|
437
|
+
if len(edges) < 20:
|
|
438
|
+
# Not enough edges for I2C
|
|
439
|
+
return [], [], {"error": "Insufficient edges for I2C decode"}
|
|
440
|
+
|
|
441
|
+
# Create synthetic SCL (toggle at each edge for analysis)
|
|
442
|
+
scl = np.ones_like(sda, dtype=bool)
|
|
443
|
+
for i, edge in enumerate(edges):
|
|
444
|
+
if i % 2 == 0 and edge + 1 < len(scl):
|
|
445
|
+
scl[edge : edge + 10] = False # Create clock pulses
|
|
446
|
+
|
|
447
|
+
decoder = I2CDecoder()
|
|
448
|
+
packets = list(decoder.decode(scl=scl, sda=sda, sample_rate=sample_rate))
|
|
449
|
+
errors = []
|
|
450
|
+
|
|
451
|
+
addresses_seen: set[int] = set()
|
|
452
|
+
for i, pkt in enumerate(packets):
|
|
453
|
+
addr = pkt.annotations.get("address", 0) if pkt.annotations else 0
|
|
454
|
+
addresses_seen.add(addr)
|
|
455
|
+
|
|
456
|
+
if pkt.errors:
|
|
457
|
+
for err in pkt.errors:
|
|
458
|
+
errors.append(
|
|
459
|
+
{
|
|
460
|
+
"packet_index": i,
|
|
461
|
+
"timestamp": f"{pkt.timestamp * 1e3:.3f} ms",
|
|
462
|
+
"type": err,
|
|
463
|
+
"address": f"0x{addr:02X}",
|
|
464
|
+
}
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Estimate clock rate from edge intervals
|
|
468
|
+
if len(edges) > 1:
|
|
469
|
+
avg_interval = np.mean(np.diff(edges)) / sample_rate
|
|
470
|
+
clock_rate = 1 / (2 * avg_interval) if avg_interval > 0 else 0
|
|
471
|
+
else:
|
|
472
|
+
clock_rate = 0
|
|
473
|
+
|
|
474
|
+
protocol_info = {
|
|
475
|
+
"clock_frequency": f"{clock_rate / 1e3:.1f} kHz" if clock_rate > 0 else "Unknown",
|
|
476
|
+
"addresses_seen": [f"0x{a:02X}" for a in sorted(addresses_seen)],
|
|
477
|
+
"transactions": len(packets),
|
|
478
|
+
"note": "Single-channel decode. For accurate I2C decode, provide separate SCL/SDA signals.",
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return packets, errors, protocol_info
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _decode_can(
|
|
485
|
+
trace: DigitalTrace,
|
|
486
|
+
baud_rate: int | None,
|
|
487
|
+
show_errors: bool,
|
|
488
|
+
) -> tuple[list[ProtocolPacket], list[dict[str, Any]], dict[str, Any]]:
|
|
489
|
+
"""Decode CAN protocol.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
trace: Digital trace to decode.
|
|
493
|
+
baud_rate: CAN bit rate (None for common rate detection).
|
|
494
|
+
show_errors: Whether to filter to errors only.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
Tuple of (packets, errors, protocol_info).
|
|
498
|
+
"""
|
|
499
|
+
from oscura.analyzers.protocols.can import CANDecoder
|
|
500
|
+
|
|
501
|
+
# Try common CAN baud rates if not specified
|
|
502
|
+
if baud_rate is None:
|
|
503
|
+
common_rates = [500000, 250000, 125000, 1000000]
|
|
504
|
+
best_rate = 500000
|
|
505
|
+
max_packets = 0
|
|
506
|
+
|
|
507
|
+
for rate in common_rates:
|
|
508
|
+
try:
|
|
509
|
+
decoder = CANDecoder(bitrate=rate)
|
|
510
|
+
test_packets = list(decoder.decode(trace))
|
|
511
|
+
if len(test_packets) > max_packets:
|
|
512
|
+
max_packets = len(test_packets)
|
|
513
|
+
best_rate = rate
|
|
514
|
+
except Exception:
|
|
515
|
+
continue
|
|
516
|
+
|
|
517
|
+
baud_rate = best_rate
|
|
518
|
+
|
|
519
|
+
decoder = CANDecoder(bitrate=baud_rate)
|
|
520
|
+
packets = list(decoder.decode(trace))
|
|
521
|
+
errors = []
|
|
522
|
+
|
|
523
|
+
arbitration_ids: set[int] = set()
|
|
524
|
+
for i, pkt in enumerate(packets):
|
|
525
|
+
arb_id = pkt.annotations.get("arbitration_id", 0) if pkt.annotations else 0
|
|
526
|
+
arbitration_ids.add(arb_id)
|
|
527
|
+
|
|
528
|
+
if pkt.errors:
|
|
529
|
+
for err in pkt.errors:
|
|
530
|
+
errors.append(
|
|
531
|
+
{
|
|
532
|
+
"packet_index": i,
|
|
533
|
+
"timestamp": f"{pkt.timestamp * 1e3:.3f} ms",
|
|
534
|
+
"type": err,
|
|
535
|
+
"arbitration_id": f"0x{arb_id:03X}",
|
|
536
|
+
}
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
protocol_info = {
|
|
540
|
+
"bit_rate": f"{baud_rate / 1000:.0f} kbps",
|
|
541
|
+
"messages": len(packets),
|
|
542
|
+
"arbitration_ids": [f"0x{a:03X}" for a in sorted(arbitration_ids)[:10]],
|
|
543
|
+
"extended_frames": sum(
|
|
544
|
+
1 for p in packets if p.annotations and p.annotations.get("is_extended")
|
|
545
|
+
),
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if len(arbitration_ids) > 10:
|
|
549
|
+
protocol_info["note"] = f"Showing first 10 of {len(arbitration_ids)} arbitration IDs"
|
|
550
|
+
|
|
551
|
+
return packets, errors, protocol_info
|