oscura 0.0.1__py3-none-any.whl → 0.1.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 +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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""ASAM MDF/MF4 (Measurement Data Format) file loader.
|
|
2
|
+
|
|
3
|
+
This module provides loading of MDF4/MF4 files using the asammdf library.
|
|
4
|
+
MDF is an industry-standard format for storing measurement data,
|
|
5
|
+
commonly used with CAN bus loggers like CANedge.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from oscura.automotive.can.models import CANMessage, CANMessageList
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
import numpy.typing as npt
|
|
17
|
+
|
|
18
|
+
__all__ = ["load_mdf"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_mdf(file_path: Path | str) -> CANMessageList:
|
|
22
|
+
"""Load CAN messages from an MDF/MF4 file.
|
|
23
|
+
|
|
24
|
+
This loader supports both MDF3 and MDF4 (MF4) formats. It handles various
|
|
25
|
+
CAN logging structures commonly found in MDF files from tools like CANedge,
|
|
26
|
+
Vector CANalyzer, ETAS INCA, and others.
|
|
27
|
+
|
|
28
|
+
The loader attempts multiple extraction strategies:
|
|
29
|
+
1. Bus logging format (CAN frames as structured records)
|
|
30
|
+
2. Signal-based format (CAN ID, data, timestamps as separate channels)
|
|
31
|
+
3. Raw frame format (binary CAN frames)
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
file_path: Path to the MDF/MF4 file.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
CANMessageList containing all parsed CAN messages.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ImportError: If asammdf is not installed.
|
|
41
|
+
FileNotFoundError: If file doesn't exist.
|
|
42
|
+
ValueError: If file cannot be parsed or contains no CAN data.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> messages = load_mdf("capture.mf4")
|
|
46
|
+
>>> print(f"Loaded {len(messages)} messages")
|
|
47
|
+
>>> print(f"Unique IDs: {len(messages.unique_ids())}")
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
from asammdf import MDF # type: ignore[import-untyped]
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
raise ImportError(
|
|
53
|
+
"asammdf is required for MDF/MF4 file support. "
|
|
54
|
+
"Install with: pip install 'oscura[automotive]'"
|
|
55
|
+
) from e
|
|
56
|
+
|
|
57
|
+
path = Path(file_path)
|
|
58
|
+
if not path.exists():
|
|
59
|
+
raise FileNotFoundError(f"MDF file not found: {path}")
|
|
60
|
+
|
|
61
|
+
messages = CANMessageList()
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Open MDF file (supports both MDF3 and MDF4)
|
|
65
|
+
with MDF(str(path)) as mdf:
|
|
66
|
+
# Strategy 1: Try to extract CAN bus logging data
|
|
67
|
+
# Look for channels with CAN-related names
|
|
68
|
+
_extract_can_bus_logging(mdf, messages)
|
|
69
|
+
|
|
70
|
+
# Strategy 2: If no messages found, try signal-based extraction
|
|
71
|
+
# Some MDF files store CAN ID, data, timestamp as separate signals
|
|
72
|
+
if not messages.messages:
|
|
73
|
+
_extract_can_signals(mdf, messages)
|
|
74
|
+
|
|
75
|
+
# Strategy 3: Try iterating through all channels looking for CAN data patterns
|
|
76
|
+
if not messages.messages:
|
|
77
|
+
_extract_can_channels(mdf, messages)
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise ValueError(f"Failed to parse MDF file {path}: {e}") from e
|
|
81
|
+
|
|
82
|
+
if not messages.messages:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"No CAN messages found in MDF file {path}. "
|
|
85
|
+
"File may not contain CAN bus data or uses unsupported format."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return messages
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _extract_can_bus_logging(mdf: Any, messages: CANMessageList) -> None:
|
|
92
|
+
"""Extract CAN messages from bus logging format.
|
|
93
|
+
|
|
94
|
+
This handles MDF files that store CAN frames as structured bus logging data,
|
|
95
|
+
common in Vector tools and CANedge loggers.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
mdf: Opened MDF file object.
|
|
99
|
+
messages: CANMessageList to append extracted messages to.
|
|
100
|
+
"""
|
|
101
|
+
# Common CAN bus logging channel patterns
|
|
102
|
+
can_bus_patterns = [
|
|
103
|
+
"CAN_DataFrame",
|
|
104
|
+
"CANBus",
|
|
105
|
+
"CAN_Message",
|
|
106
|
+
"CAN.DataFrames",
|
|
107
|
+
"CAN Bus",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
for channel_name in mdf.channels_db:
|
|
111
|
+
# Check if channel name matches CAN bus logging pattern
|
|
112
|
+
if any(pattern in channel_name for pattern in can_bus_patterns):
|
|
113
|
+
try:
|
|
114
|
+
signal = mdf.get(channel_name)
|
|
115
|
+
|
|
116
|
+
# Extract timestamps
|
|
117
|
+
timestamps = signal.timestamps
|
|
118
|
+
|
|
119
|
+
# CAN data could be in samples (structured) or as raw bytes
|
|
120
|
+
samples = signal.samples
|
|
121
|
+
|
|
122
|
+
# Handle different sample structures
|
|
123
|
+
if hasattr(samples, "dtype") and samples.dtype.names:
|
|
124
|
+
# Structured array with fields
|
|
125
|
+
_extract_structured_can_frames(samples, timestamps, messages)
|
|
126
|
+
else:
|
|
127
|
+
# Try to interpret as raw CAN frames
|
|
128
|
+
_extract_raw_can_frames(samples, timestamps, messages)
|
|
129
|
+
|
|
130
|
+
except Exception:
|
|
131
|
+
# Skip channels that fail to parse
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _extract_structured_can_frames(
|
|
136
|
+
samples: npt.NDArray[Any], timestamps: npt.NDArray[Any], messages: CANMessageList
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Extract CAN messages from structured array format.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
samples: Structured numpy array with CAN frame fields.
|
|
142
|
+
timestamps: Array of timestamps.
|
|
143
|
+
messages: CANMessageList to append to.
|
|
144
|
+
"""
|
|
145
|
+
# Common field names in structured CAN logging
|
|
146
|
+
id_fields = ["ID", "id", "BusID", "Identifier", "ArbitrationID"]
|
|
147
|
+
data_fields = ["Data", "data", "DataBytes", "Payload"]
|
|
148
|
+
dlc_fields = ["DLC", "dlc", "DataLength"]
|
|
149
|
+
|
|
150
|
+
# Find which fields are present
|
|
151
|
+
field_names = samples.dtype.names
|
|
152
|
+
if not field_names:
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
id_field = next((f for f in id_fields if f in field_names), None)
|
|
156
|
+
data_field = next((f for f in data_fields if f in field_names), None)
|
|
157
|
+
dlc_field = next((f for f in dlc_fields if f in field_names), None)
|
|
158
|
+
|
|
159
|
+
if not id_field:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
for i in range(len(samples)):
|
|
163
|
+
try:
|
|
164
|
+
# Extract CAN ID
|
|
165
|
+
arb_id = int(samples[id_field][i])
|
|
166
|
+
|
|
167
|
+
# Extract data bytes
|
|
168
|
+
if data_field:
|
|
169
|
+
data_bytes = samples[data_field][i]
|
|
170
|
+
if isinstance(data_bytes, bytes):
|
|
171
|
+
data = data_bytes
|
|
172
|
+
else:
|
|
173
|
+
# Convert array to bytes
|
|
174
|
+
data = bytes(data_bytes)
|
|
175
|
+
else:
|
|
176
|
+
data = b""
|
|
177
|
+
|
|
178
|
+
# Extract DLC if available
|
|
179
|
+
if dlc_field:
|
|
180
|
+
dlc = int(samples[dlc_field][i])
|
|
181
|
+
data = data[:dlc]
|
|
182
|
+
|
|
183
|
+
# Determine if extended ID
|
|
184
|
+
is_extended = arb_id > 0x7FF
|
|
185
|
+
|
|
186
|
+
# Create message
|
|
187
|
+
can_msg = CANMessage(
|
|
188
|
+
arbitration_id=arb_id,
|
|
189
|
+
timestamp=float(timestamps[i]),
|
|
190
|
+
data=data,
|
|
191
|
+
is_extended=is_extended,
|
|
192
|
+
is_fd=False, # MDF doesn't typically indicate CAN-FD
|
|
193
|
+
channel=0, # Channel info not always available in MDF
|
|
194
|
+
)
|
|
195
|
+
messages.append(can_msg)
|
|
196
|
+
|
|
197
|
+
except (IndexError, ValueError, TypeError):
|
|
198
|
+
# Skip malformed frames
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _extract_raw_can_frames(
|
|
203
|
+
samples: npt.NDArray[Any], timestamps: npt.NDArray[Any], messages: CANMessageList
|
|
204
|
+
) -> None:
|
|
205
|
+
"""Extract CAN messages from raw byte format.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
samples: Raw bytes array.
|
|
209
|
+
timestamps: Array of timestamps.
|
|
210
|
+
messages: CANMessageList to append to.
|
|
211
|
+
"""
|
|
212
|
+
# Attempt to parse raw CAN frames
|
|
213
|
+
# Typical raw CAN frame: [ID (4 bytes)] [DLC (1 byte)] [Data (up to 8 bytes)]
|
|
214
|
+
for i in range(len(samples)):
|
|
215
|
+
try:
|
|
216
|
+
frame = samples[i]
|
|
217
|
+
if isinstance(frame, bytes) and len(frame) >= 5:
|
|
218
|
+
# Parse standard raw CAN frame structure
|
|
219
|
+
arb_id = int.from_bytes(frame[0:4], byteorder="little")
|
|
220
|
+
dlc = min(frame[4], 8)
|
|
221
|
+
data = frame[5 : 5 + dlc]
|
|
222
|
+
|
|
223
|
+
# Determine if extended ID
|
|
224
|
+
is_extended = arb_id > 0x7FF
|
|
225
|
+
|
|
226
|
+
can_msg = CANMessage(
|
|
227
|
+
arbitration_id=arb_id,
|
|
228
|
+
timestamp=float(timestamps[i]),
|
|
229
|
+
data=data,
|
|
230
|
+
is_extended=is_extended,
|
|
231
|
+
is_fd=False,
|
|
232
|
+
channel=0,
|
|
233
|
+
)
|
|
234
|
+
messages.append(can_msg)
|
|
235
|
+
|
|
236
|
+
except (IndexError, ValueError, TypeError):
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _extract_can_signals(mdf: Any, messages: CANMessageList) -> None:
|
|
241
|
+
"""Extract CAN messages from separate signal channels.
|
|
242
|
+
|
|
243
|
+
This handles MDF files where CAN ID, data, and timestamps are stored
|
|
244
|
+
as separate channels/signals.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
mdf: Opened MDF file object.
|
|
248
|
+
messages: CANMessageList to append to.
|
|
249
|
+
"""
|
|
250
|
+
# Look for separate CAN ID and data channels
|
|
251
|
+
id_patterns = ["CAN_ID", "CANID", "CAN.ID", "Identifier"]
|
|
252
|
+
data_patterns = ["CAN_Data", "CANData", "CAN.Data", "Payload"]
|
|
253
|
+
|
|
254
|
+
id_channels = []
|
|
255
|
+
data_channels = []
|
|
256
|
+
|
|
257
|
+
for channel_name in mdf.channels_db:
|
|
258
|
+
if any(pattern in channel_name for pattern in id_patterns):
|
|
259
|
+
id_channels.append(channel_name)
|
|
260
|
+
elif any(pattern in channel_name for pattern in data_patterns):
|
|
261
|
+
data_channels.append(channel_name)
|
|
262
|
+
|
|
263
|
+
# Try to match ID and data channels
|
|
264
|
+
for id_channel in id_channels:
|
|
265
|
+
for data_channel in data_channels:
|
|
266
|
+
try:
|
|
267
|
+
id_signal = mdf.get(id_channel)
|
|
268
|
+
data_signal = mdf.get(data_channel)
|
|
269
|
+
|
|
270
|
+
# Ensure same length
|
|
271
|
+
if len(id_signal.timestamps) != len(data_signal.timestamps):
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# Extract messages
|
|
275
|
+
for i in range(len(id_signal.timestamps)):
|
|
276
|
+
arb_id = int(id_signal.samples[i])
|
|
277
|
+
data = bytes(data_signal.samples[i])
|
|
278
|
+
timestamp = float(id_signal.timestamps[i])
|
|
279
|
+
|
|
280
|
+
is_extended = arb_id > 0x7FF
|
|
281
|
+
|
|
282
|
+
can_msg = CANMessage(
|
|
283
|
+
arbitration_id=arb_id,
|
|
284
|
+
timestamp=timestamp,
|
|
285
|
+
data=data,
|
|
286
|
+
is_extended=is_extended,
|
|
287
|
+
is_fd=False,
|
|
288
|
+
channel=0,
|
|
289
|
+
)
|
|
290
|
+
messages.append(can_msg)
|
|
291
|
+
|
|
292
|
+
except Exception:
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _extract_can_channels(mdf: Any, messages: CANMessageList) -> None:
|
|
297
|
+
"""Extract CAN messages by searching all channels for CAN patterns.
|
|
298
|
+
|
|
299
|
+
This is a fallback strategy that looks for any channel containing
|
|
300
|
+
'CAN' in the name and attempts to extract CAN data.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
mdf: Opened MDF file object.
|
|
304
|
+
messages: CANMessageList to append to.
|
|
305
|
+
"""
|
|
306
|
+
can_keywords = ["CAN", "can"]
|
|
307
|
+
|
|
308
|
+
for channel_name in mdf.channels_db:
|
|
309
|
+
# Only process channels with CAN in the name
|
|
310
|
+
if not any(keyword in channel_name for keyword in can_keywords):
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
signal = mdf.get(channel_name)
|
|
315
|
+
timestamps = signal.timestamps
|
|
316
|
+
samples = signal.samples
|
|
317
|
+
|
|
318
|
+
# Try different interpretations
|
|
319
|
+
if hasattr(samples, "dtype"):
|
|
320
|
+
if samples.dtype.names:
|
|
321
|
+
# Structured
|
|
322
|
+
_extract_structured_can_frames(samples, timestamps, messages)
|
|
323
|
+
elif samples.dtype == "uint32" or samples.dtype == "int32":
|
|
324
|
+
# Could be CAN IDs
|
|
325
|
+
pass
|
|
326
|
+
elif len(samples.shape) == 2 and samples.shape[1] >= 8:
|
|
327
|
+
# Could be CAN data bytes
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
except Exception:
|
|
331
|
+
continue
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""PCAP file loader for CAN bus data.
|
|
2
|
+
|
|
3
|
+
This module provides loading of PCAP files containing SocketCAN frames.
|
|
4
|
+
PCAP is a common packet capture format that can contain CAN frames from
|
|
5
|
+
network interfaces or recorded with tools like Wireshark or tcpdump.
|
|
6
|
+
|
|
7
|
+
Supported formats:
|
|
8
|
+
- SocketCAN frames (Linux can0, can1, etc.)
|
|
9
|
+
- CAN frames from pcap-ng format
|
|
10
|
+
|
|
11
|
+
Requirements:
|
|
12
|
+
- scapy library (install with: uv pip install scapy)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
from oscura.automotive.can.models import CANMessage, CANMessageList
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from scapy.packet import Packet # type: ignore[import-not-found]
|
|
24
|
+
|
|
25
|
+
__all__ = ["load_pcap"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_pcap(file_path: Path | str) -> CANMessageList:
|
|
29
|
+
"""Load CAN messages from a PCAP file.
|
|
30
|
+
|
|
31
|
+
This function reads PCAP files containing SocketCAN frames and converts
|
|
32
|
+
them to TraceKit's CANMessage format. It uses scapy to parse the PCAP
|
|
33
|
+
file and extract CAN frames.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
file_path: Path to the PCAP or PCAPNG file.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
CANMessageList containing all parsed CAN messages.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
FileNotFoundError: If file doesn't exist.
|
|
43
|
+
ImportError: If scapy is not installed.
|
|
44
|
+
ValueError: If file cannot be parsed or contains no CAN frames.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> messages = load_pcap("capture.pcap")
|
|
48
|
+
>>> print(f"Loaded {len(messages)} messages")
|
|
49
|
+
|
|
50
|
+
Note:
|
|
51
|
+
Requires scapy to be installed:
|
|
52
|
+
uv pip install oscura[automotive]
|
|
53
|
+
|
|
54
|
+
Or manually:
|
|
55
|
+
uv pip install scapy
|
|
56
|
+
"""
|
|
57
|
+
path = Path(file_path)
|
|
58
|
+
if not path.exists():
|
|
59
|
+
raise FileNotFoundError(f"PCAP file not found: {path}")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
from scapy.all import rdpcap # type: ignore[import-not-found]
|
|
63
|
+
from scapy.layers.can import CAN # type: ignore[import-not-found]
|
|
64
|
+
except ImportError as e:
|
|
65
|
+
msg = "scapy library is required for PCAP loading. Install with: uv pip install scapy"
|
|
66
|
+
raise ImportError(msg) from e
|
|
67
|
+
|
|
68
|
+
messages = CANMessageList()
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# Read PCAP file
|
|
72
|
+
packets = rdpcap(str(path))
|
|
73
|
+
|
|
74
|
+
# Extract CAN frames
|
|
75
|
+
first_timestamp: float | None = None
|
|
76
|
+
for packet in packets:
|
|
77
|
+
# Check if packet contains CAN layer
|
|
78
|
+
if CAN in packet:
|
|
79
|
+
can_frame: Packet = packet[CAN]
|
|
80
|
+
|
|
81
|
+
# Get timestamp
|
|
82
|
+
if hasattr(packet, "time"):
|
|
83
|
+
if first_timestamp is None:
|
|
84
|
+
first_timestamp = float(packet.time)
|
|
85
|
+
timestamp = float(packet.time) - first_timestamp
|
|
86
|
+
else:
|
|
87
|
+
timestamp = 0.0
|
|
88
|
+
|
|
89
|
+
# Extract CAN ID and data
|
|
90
|
+
arb_id = int(can_frame.identifier)
|
|
91
|
+
|
|
92
|
+
# Get data bytes (scapy stores CAN data as bytes)
|
|
93
|
+
if hasattr(can_frame, "data"):
|
|
94
|
+
data = bytes(can_frame.data)
|
|
95
|
+
else:
|
|
96
|
+
data = b""
|
|
97
|
+
|
|
98
|
+
# Determine if extended ID (bit 31 indicates extended format)
|
|
99
|
+
# SocketCAN uses bit 31 for extended frame flag
|
|
100
|
+
is_extended = bool(arb_id & 0x80000000)
|
|
101
|
+
if is_extended:
|
|
102
|
+
arb_id = arb_id & 0x1FFFFFFF # Mask to get 29-bit ID
|
|
103
|
+
|
|
104
|
+
# Determine if CAN-FD (scapy may have an FD flag)
|
|
105
|
+
is_fd = hasattr(can_frame, "flags") and (can_frame.flags & 0x01)
|
|
106
|
+
|
|
107
|
+
# Extract channel if available
|
|
108
|
+
channel = 0
|
|
109
|
+
if hasattr(can_frame, "channel"):
|
|
110
|
+
channel = int(can_frame.channel)
|
|
111
|
+
|
|
112
|
+
# Create CANMessage
|
|
113
|
+
can_msg = CANMessage(
|
|
114
|
+
arbitration_id=arb_id,
|
|
115
|
+
timestamp=timestamp,
|
|
116
|
+
data=data,
|
|
117
|
+
is_extended=is_extended,
|
|
118
|
+
is_fd=is_fd,
|
|
119
|
+
channel=channel,
|
|
120
|
+
)
|
|
121
|
+
messages.append(can_msg)
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise ValueError(f"Failed to parse PCAP file {path}: {e}") from e
|
|
125
|
+
|
|
126
|
+
if len(messages) == 0:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"No CAN frames found in PCAP file {path}. "
|
|
129
|
+
"Ensure the capture contains SocketCAN or CAN frames."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return messages
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""OBD-II diagnostic protocol support.
|
|
2
|
+
|
|
3
|
+
This module provides OBD-II (On-Board Diagnostics) protocol decoding
|
|
4
|
+
for standard vehicle diagnostics.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__all__ = ["PID", "OBD2Decoder", "OBD2Response"]
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from oscura.automotive.obd.decoder import PID, OBD2Decoder, OBD2Response
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|