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,212 @@
|
|
|
1
|
+
"""Signal and message correlation analysis.
|
|
2
|
+
|
|
3
|
+
This module provides correlation analysis for CAN signals to discover
|
|
4
|
+
relationships between different signals and messages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from scipy.stats import pearsonr
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from oscura.automotive.can.models import CANMessageList, SignalDefinition
|
|
16
|
+
from oscura.automotive.can.session import CANSession
|
|
17
|
+
|
|
18
|
+
__all__ = ["CorrelationAnalyzer"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CorrelationAnalyzer:
|
|
22
|
+
"""Analyze correlations between CAN signals and messages."""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def correlate_signals(
|
|
26
|
+
messages1: CANMessageList,
|
|
27
|
+
signal_def1: SignalDefinition,
|
|
28
|
+
messages2: CANMessageList,
|
|
29
|
+
signal_def2: SignalDefinition,
|
|
30
|
+
max_time_shift: float = 0.0,
|
|
31
|
+
) -> dict[str, float | int]:
|
|
32
|
+
"""Compute correlation between two signals.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
messages1: Messages containing first signal.
|
|
36
|
+
signal_def1: First signal definition.
|
|
37
|
+
messages2: Messages containing second signal.
|
|
38
|
+
signal_def2: Second signal definition.
|
|
39
|
+
max_time_shift: Maximum time shift to consider (seconds).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dictionary with correlation results:
|
|
43
|
+
- correlation: Pearson correlation coefficient
|
|
44
|
+
- p_value: Statistical significance
|
|
45
|
+
- lag: Time lag with maximum correlation (if time shift enabled)
|
|
46
|
+
- sample_count: Number of samples used
|
|
47
|
+
"""
|
|
48
|
+
# Decode signals
|
|
49
|
+
values1 = []
|
|
50
|
+
timestamps1 = []
|
|
51
|
+
for msg in messages1.messages:
|
|
52
|
+
try:
|
|
53
|
+
value = signal_def1.decode(msg.data)
|
|
54
|
+
values1.append(value)
|
|
55
|
+
timestamps1.append(msg.timestamp)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
values2 = []
|
|
60
|
+
timestamps2 = []
|
|
61
|
+
for msg in messages2.messages:
|
|
62
|
+
try:
|
|
63
|
+
value = signal_def2.decode(msg.data)
|
|
64
|
+
values2.append(value)
|
|
65
|
+
timestamps2.append(msg.timestamp)
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
if len(values1) < 2 or len(values2) < 2:
|
|
70
|
+
return {
|
|
71
|
+
"correlation": 0.0,
|
|
72
|
+
"p_value": 1.0,
|
|
73
|
+
"lag": 0.0,
|
|
74
|
+
"sample_count": 0,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Align signals by timestamp (simple approach: nearest neighbor)
|
|
78
|
+
aligned_values1 = []
|
|
79
|
+
aligned_values2 = []
|
|
80
|
+
|
|
81
|
+
for t1, v1 in zip(timestamps1, values1, strict=False):
|
|
82
|
+
# Find closest timestamp in signal 2
|
|
83
|
+
time_diffs = [abs(t2 - t1) for t2 in timestamps2]
|
|
84
|
+
if min(time_diffs) <= max(0.1, max_time_shift): # Within 100ms or max_time_shift
|
|
85
|
+
closest_idx = time_diffs.index(min(time_diffs))
|
|
86
|
+
aligned_values1.append(v1)
|
|
87
|
+
aligned_values2.append(values2[closest_idx])
|
|
88
|
+
|
|
89
|
+
if len(aligned_values1) < 2:
|
|
90
|
+
return {
|
|
91
|
+
"correlation": 0.0,
|
|
92
|
+
"p_value": 1.0,
|
|
93
|
+
"lag": 0.0,
|
|
94
|
+
"sample_count": 0,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# Compute Pearson correlation
|
|
98
|
+
arr1 = np.array(aligned_values1)
|
|
99
|
+
arr2 = np.array(aligned_values2)
|
|
100
|
+
|
|
101
|
+
correlation, p_value = pearsonr(arr1, arr2)
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
"correlation": float(correlation),
|
|
105
|
+
"p_value": float(p_value),
|
|
106
|
+
"lag": 0.0,
|
|
107
|
+
"sample_count": len(aligned_values1),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def correlate_bytes(
|
|
112
|
+
messages1: CANMessageList,
|
|
113
|
+
byte_pos1: int,
|
|
114
|
+
messages2: CANMessageList,
|
|
115
|
+
byte_pos2: int,
|
|
116
|
+
) -> float:
|
|
117
|
+
"""Compute correlation between two byte positions.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
messages1: First message collection.
|
|
121
|
+
byte_pos1: Byte position in first message.
|
|
122
|
+
messages2: Second message collection.
|
|
123
|
+
byte_pos2: Byte position in second message.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Pearson correlation coefficient (-1.0 to 1.0).
|
|
127
|
+
"""
|
|
128
|
+
# Extract byte values
|
|
129
|
+
values1 = []
|
|
130
|
+
for msg in messages1.messages:
|
|
131
|
+
if len(msg.data) > byte_pos1:
|
|
132
|
+
values1.append(msg.data[byte_pos1])
|
|
133
|
+
|
|
134
|
+
values2 = []
|
|
135
|
+
for msg in messages2.messages:
|
|
136
|
+
if len(msg.data) > byte_pos2:
|
|
137
|
+
values2.append(msg.data[byte_pos2])
|
|
138
|
+
|
|
139
|
+
if len(values1) < 2 or len(values2) < 2:
|
|
140
|
+
return 0.0
|
|
141
|
+
|
|
142
|
+
# Truncate to same length
|
|
143
|
+
min_len = min(len(values1), len(values2))
|
|
144
|
+
values1 = values1[:min_len]
|
|
145
|
+
values2 = values2[:min_len]
|
|
146
|
+
|
|
147
|
+
# Compute correlation
|
|
148
|
+
arr1 = np.array(values1, dtype=float)
|
|
149
|
+
arr2 = np.array(values2, dtype=float)
|
|
150
|
+
|
|
151
|
+
# Check for zero variance
|
|
152
|
+
if np.std(arr1) == 0 or np.std(arr2) == 0:
|
|
153
|
+
return 0.0
|
|
154
|
+
|
|
155
|
+
correlation, _ = pearsonr(arr1, arr2)
|
|
156
|
+
|
|
157
|
+
return float(correlation)
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def find_correlated_messages(
|
|
161
|
+
session: CANSession,
|
|
162
|
+
arbitration_id: int,
|
|
163
|
+
threshold: float = 0.7,
|
|
164
|
+
) -> dict[int, float]:
|
|
165
|
+
"""Find messages correlated with a given message ID.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
session: CANSession to analyze.
|
|
169
|
+
arbitration_id: CAN ID to find correlations for.
|
|
170
|
+
threshold: Minimum correlation threshold (0.0-1.0).
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary mapping correlated message IDs to correlation scores.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
correlations = {}
|
|
177
|
+
|
|
178
|
+
# Get messages for the target ID
|
|
179
|
+
target_messages = session._messages.filter_by_id(arbitration_id)
|
|
180
|
+
if not target_messages.messages:
|
|
181
|
+
return {}
|
|
182
|
+
|
|
183
|
+
# Determine max DLC for target
|
|
184
|
+
max_dlc_target = max(msg.dlc for msg in target_messages.messages)
|
|
185
|
+
|
|
186
|
+
# Check all other unique IDs
|
|
187
|
+
for other_id in session.unique_ids():
|
|
188
|
+
if other_id == arbitration_id:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
other_messages = session._messages.filter_by_id(other_id)
|
|
192
|
+
max_dlc_other = max(msg.dlc for msg in other_messages.messages)
|
|
193
|
+
|
|
194
|
+
# Check byte-by-byte correlation
|
|
195
|
+
max_correlation = 0.0
|
|
196
|
+
|
|
197
|
+
for byte_pos_target in range(max_dlc_target):
|
|
198
|
+
for byte_pos_other in range(max_dlc_other):
|
|
199
|
+
corr = CorrelationAnalyzer.correlate_bytes(
|
|
200
|
+
target_messages,
|
|
201
|
+
byte_pos_target,
|
|
202
|
+
other_messages,
|
|
203
|
+
byte_pos_other,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
if abs(corr) > abs(max_correlation):
|
|
207
|
+
max_correlation = corr
|
|
208
|
+
|
|
209
|
+
if abs(max_correlation) >= threshold:
|
|
210
|
+
correlations[other_id] = max_correlation
|
|
211
|
+
|
|
212
|
+
return correlations
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
"""Discovery documentation for CAN reverse engineering.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to save and load CAN reverse engineering
|
|
4
|
+
discoveries in the .tkcan format (YAML-based with evidence tracking).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import asdict, dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from oscura.automotive.can.models import SignalDefinition
|
|
18
|
+
|
|
19
|
+
__all__ = ["DiscoveryDocument", "Hypothesis", "MessageDiscovery", "SignalDiscovery"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SignalDiscovery:
|
|
24
|
+
"""Documented signal discovery.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
name: Signal name.
|
|
28
|
+
start_bit: Starting bit position.
|
|
29
|
+
length: Signal length in bits.
|
|
30
|
+
byte_order: Byte order.
|
|
31
|
+
value_type: Value type.
|
|
32
|
+
scale: Scaling factor.
|
|
33
|
+
offset: Offset value.
|
|
34
|
+
unit: Physical unit.
|
|
35
|
+
min_value: Observed minimum value.
|
|
36
|
+
max_value: Observed maximum value.
|
|
37
|
+
confidence: Confidence score (0.0-1.0).
|
|
38
|
+
evidence: List of evidence supporting this discovery.
|
|
39
|
+
comment: Additional notes.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
name: str
|
|
43
|
+
start_bit: int
|
|
44
|
+
length: int
|
|
45
|
+
byte_order: Literal["big_endian", "little_endian"] = "big_endian"
|
|
46
|
+
value_type: Literal["unsigned", "signed", "float"] = "unsigned"
|
|
47
|
+
scale: float = 1.0
|
|
48
|
+
offset: float = 0.0
|
|
49
|
+
unit: str = ""
|
|
50
|
+
min_value: float | None = None
|
|
51
|
+
max_value: float | None = None
|
|
52
|
+
confidence: float = 0.0
|
|
53
|
+
evidence: list[str] = field(default_factory=list)
|
|
54
|
+
comment: str = ""
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> dict[str, Any]:
|
|
57
|
+
"""Convert to dictionary."""
|
|
58
|
+
return {
|
|
59
|
+
k: v
|
|
60
|
+
for k, v in asdict(self).items()
|
|
61
|
+
if v is not None and (not isinstance(v, list) or v)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_definition(
|
|
66
|
+
cls,
|
|
67
|
+
definition: SignalDefinition,
|
|
68
|
+
confidence: float = 1.0,
|
|
69
|
+
evidence: list[str] | None = None,
|
|
70
|
+
) -> SignalDiscovery:
|
|
71
|
+
"""Create from SignalDefinition.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
definition: Signal definition.
|
|
75
|
+
confidence: Confidence score.
|
|
76
|
+
evidence: Evidence list.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
SignalDiscovery instance.
|
|
80
|
+
"""
|
|
81
|
+
return cls(
|
|
82
|
+
name=definition.name,
|
|
83
|
+
start_bit=definition.start_bit,
|
|
84
|
+
length=definition.length,
|
|
85
|
+
byte_order=definition.byte_order,
|
|
86
|
+
value_type=definition.value_type,
|
|
87
|
+
scale=definition.scale,
|
|
88
|
+
offset=definition.offset,
|
|
89
|
+
unit=definition.unit,
|
|
90
|
+
min_value=definition.min_value,
|
|
91
|
+
max_value=definition.max_value,
|
|
92
|
+
confidence=confidence,
|
|
93
|
+
evidence=evidence or [],
|
|
94
|
+
comment=definition.comment,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@dataclass
|
|
99
|
+
class Hypothesis:
|
|
100
|
+
"""A hypothesis under testing.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
message_id: CAN ID this hypothesis applies to.
|
|
104
|
+
signal: Signal name.
|
|
105
|
+
hypothesis: Description of hypothesis.
|
|
106
|
+
status: Status ('testing', 'confirmed', 'rejected').
|
|
107
|
+
test_plan: Test plan or next steps.
|
|
108
|
+
created: Creation timestamp.
|
|
109
|
+
updated: Last update timestamp.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
message_id: int
|
|
113
|
+
signal: str
|
|
114
|
+
hypothesis: str
|
|
115
|
+
status: Literal["testing", "confirmed", "rejected"] = "testing"
|
|
116
|
+
test_plan: str = ""
|
|
117
|
+
created: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
118
|
+
updated: str = field(default_factory=lambda: datetime.now().isoformat())
|
|
119
|
+
|
|
120
|
+
def to_dict(self) -> dict[str, Any]:
|
|
121
|
+
"""Convert to dictionary."""
|
|
122
|
+
return {
|
|
123
|
+
"message_id": f"0x{self.message_id:03X}",
|
|
124
|
+
"signal": self.signal,
|
|
125
|
+
"hypothesis": self.hypothesis,
|
|
126
|
+
"status": self.status,
|
|
127
|
+
"test_plan": self.test_plan,
|
|
128
|
+
"created": self.created,
|
|
129
|
+
"updated": self.updated,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dataclass
|
|
134
|
+
class MessageDiscovery:
|
|
135
|
+
"""Documented message discovery.
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
id: CAN arbitration ID.
|
|
139
|
+
name: Message name.
|
|
140
|
+
length: Data length (bytes).
|
|
141
|
+
transmitter: Transmitter node (if known).
|
|
142
|
+
cycle_time_ms: Message period in milliseconds.
|
|
143
|
+
confidence: Confidence score (0.0-1.0).
|
|
144
|
+
evidence: List of evidence supporting this discovery.
|
|
145
|
+
signals: List of discovered signals.
|
|
146
|
+
comment: Additional notes.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
id: int
|
|
150
|
+
name: str
|
|
151
|
+
length: int
|
|
152
|
+
transmitter: str | None = None
|
|
153
|
+
cycle_time_ms: float | None = None
|
|
154
|
+
confidence: float = 0.0
|
|
155
|
+
evidence: list[str] = field(default_factory=list)
|
|
156
|
+
signals: list[SignalDiscovery] = field(default_factory=list)
|
|
157
|
+
comment: str = ""
|
|
158
|
+
|
|
159
|
+
def to_dict(self) -> dict[str, Any]:
|
|
160
|
+
"""Convert to dictionary."""
|
|
161
|
+
return {
|
|
162
|
+
"id": f"0x{self.id:03X}",
|
|
163
|
+
"name": self.name,
|
|
164
|
+
"length": self.length,
|
|
165
|
+
"transmitter": self.transmitter,
|
|
166
|
+
"cycle_time_ms": self.cycle_time_ms,
|
|
167
|
+
"confidence": self.confidence,
|
|
168
|
+
"evidence": self.evidence if self.evidence else None,
|
|
169
|
+
"signals": [sig.to_dict() for sig in self.signals] if self.signals else None,
|
|
170
|
+
"comment": self.comment if self.comment else None,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class VehicleInfo:
|
|
176
|
+
"""Vehicle information.
|
|
177
|
+
|
|
178
|
+
Attributes:
|
|
179
|
+
make: Vehicle manufacturer.
|
|
180
|
+
model: Vehicle model.
|
|
181
|
+
year: Model year.
|
|
182
|
+
vin: Vehicle Identification Number.
|
|
183
|
+
notes: Additional notes.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
make: str = "Unknown"
|
|
187
|
+
model: str = "Unknown"
|
|
188
|
+
year: str | None = None
|
|
189
|
+
vin: str | None = None
|
|
190
|
+
notes: str = ""
|
|
191
|
+
|
|
192
|
+
def to_dict(self) -> dict[str, Any]:
|
|
193
|
+
"""Convert to dictionary."""
|
|
194
|
+
return {k: v for k, v in asdict(self).items() if v}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class DiscoveryDocument:
|
|
198
|
+
"""CAN reverse engineering discovery document.
|
|
199
|
+
|
|
200
|
+
This class manages a collection of discovered messages, signals,
|
|
201
|
+
and hypotheses with evidence tracking.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
def __init__(self) -> None:
|
|
205
|
+
"""Initialize empty discovery document."""
|
|
206
|
+
self.format_version = "1.0"
|
|
207
|
+
self.vehicle = VehicleInfo()
|
|
208
|
+
self.messages: dict[int, MessageDiscovery] = {}
|
|
209
|
+
self.hypotheses: list[Hypothesis] = []
|
|
210
|
+
|
|
211
|
+
def add_message(self, discovery: MessageDiscovery) -> None:
|
|
212
|
+
"""Add message discovery.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
discovery: MessageDiscovery to add.
|
|
216
|
+
"""
|
|
217
|
+
self.messages[discovery.id] = discovery
|
|
218
|
+
|
|
219
|
+
def add_hypothesis(self, hypothesis: Hypothesis) -> None:
|
|
220
|
+
"""Add hypothesis.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
hypothesis: Hypothesis to add.
|
|
224
|
+
"""
|
|
225
|
+
self.hypotheses.append(hypothesis)
|
|
226
|
+
|
|
227
|
+
def save(self, file_path: Path | str) -> None:
|
|
228
|
+
"""Save discoveries to .tkcan file.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
file_path: Output file path.
|
|
232
|
+
"""
|
|
233
|
+
path = Path(file_path)
|
|
234
|
+
|
|
235
|
+
# Build YAML structure
|
|
236
|
+
doc = {
|
|
237
|
+
"format_version": self.format_version,
|
|
238
|
+
"vehicle": self.vehicle.to_dict(),
|
|
239
|
+
"messages": [
|
|
240
|
+
msg.to_dict() for msg in sorted(self.messages.values(), key=lambda m: m.id)
|
|
241
|
+
],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if self.hypotheses:
|
|
245
|
+
doc["hypotheses"] = [h.to_dict() for h in self.hypotheses]
|
|
246
|
+
|
|
247
|
+
# Write YAML
|
|
248
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
249
|
+
yaml.safe_dump(doc, f, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
252
|
+
def load(cls, file_path: Path | str) -> DiscoveryDocument:
|
|
253
|
+
"""Load discoveries from .tkcan file.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
file_path: Input file path.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
DiscoveryDocument instance.
|
|
260
|
+
"""
|
|
261
|
+
path = Path(file_path)
|
|
262
|
+
|
|
263
|
+
with open(path, encoding="utf-8") as f:
|
|
264
|
+
data = yaml.safe_load(f)
|
|
265
|
+
|
|
266
|
+
doc = cls()
|
|
267
|
+
|
|
268
|
+
# Load metadata
|
|
269
|
+
doc.format_version = data.get("format_version", "1.0")
|
|
270
|
+
|
|
271
|
+
# Load vehicle info
|
|
272
|
+
if "vehicle" in data:
|
|
273
|
+
v = data["vehicle"]
|
|
274
|
+
doc.vehicle = VehicleInfo(
|
|
275
|
+
make=v.get("make", "Unknown"),
|
|
276
|
+
model=v.get("model", "Unknown"),
|
|
277
|
+
year=v.get("year"),
|
|
278
|
+
vin=v.get("vin"),
|
|
279
|
+
notes=v.get("notes", ""),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Load messages
|
|
283
|
+
for msg_data in data.get("messages", []):
|
|
284
|
+
# Parse ID
|
|
285
|
+
id_str = msg_data["id"]
|
|
286
|
+
if isinstance(id_str, str):
|
|
287
|
+
msg_id = int(id_str, 16) if id_str.startswith("0x") else int(id_str)
|
|
288
|
+
else:
|
|
289
|
+
msg_id = msg_data["id"]
|
|
290
|
+
|
|
291
|
+
# Parse signals
|
|
292
|
+
signals = []
|
|
293
|
+
for sig_data in msg_data.get("signals", []):
|
|
294
|
+
sig = SignalDiscovery(
|
|
295
|
+
name=sig_data["name"],
|
|
296
|
+
start_bit=sig_data["start_bit"],
|
|
297
|
+
length=sig_data["length"],
|
|
298
|
+
byte_order=sig_data.get("byte_order", "big_endian"),
|
|
299
|
+
value_type=sig_data.get("value_type", "unsigned"),
|
|
300
|
+
scale=sig_data.get("scale", 1.0),
|
|
301
|
+
offset=sig_data.get("offset", 0.0),
|
|
302
|
+
unit=sig_data.get("unit", ""),
|
|
303
|
+
min_value=sig_data.get("min_value"),
|
|
304
|
+
max_value=sig_data.get("max_value"),
|
|
305
|
+
confidence=sig_data.get("confidence", 0.0),
|
|
306
|
+
evidence=sig_data.get("evidence", []),
|
|
307
|
+
comment=sig_data.get("comment", ""),
|
|
308
|
+
)
|
|
309
|
+
signals.append(sig)
|
|
310
|
+
|
|
311
|
+
# Create message discovery
|
|
312
|
+
msg_discovery = MessageDiscovery(
|
|
313
|
+
id=msg_id,
|
|
314
|
+
name=msg_data["name"],
|
|
315
|
+
length=msg_data["length"],
|
|
316
|
+
transmitter=msg_data.get("transmitter"),
|
|
317
|
+
cycle_time_ms=msg_data.get("cycle_time_ms"),
|
|
318
|
+
confidence=msg_data.get("confidence", 0.0),
|
|
319
|
+
evidence=msg_data.get("evidence", []),
|
|
320
|
+
signals=signals,
|
|
321
|
+
comment=msg_data.get("comment", ""),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
doc.add_message(msg_discovery)
|
|
325
|
+
|
|
326
|
+
# Load hypotheses
|
|
327
|
+
for hyp_data in data.get("hypotheses", []):
|
|
328
|
+
# Parse message ID
|
|
329
|
+
id_str = hyp_data["message_id"]
|
|
330
|
+
if isinstance(id_str, str):
|
|
331
|
+
msg_id = int(id_str, 16) if id_str.startswith("0x") else int(id_str)
|
|
332
|
+
else:
|
|
333
|
+
msg_id = hyp_data["message_id"]
|
|
334
|
+
|
|
335
|
+
hyp = Hypothesis(
|
|
336
|
+
message_id=msg_id,
|
|
337
|
+
signal=hyp_data["signal"],
|
|
338
|
+
hypothesis=hyp_data["hypothesis"],
|
|
339
|
+
status=hyp_data.get("status", "testing"),
|
|
340
|
+
test_plan=hyp_data.get("test_plan", ""),
|
|
341
|
+
created=hyp_data.get("created", datetime.now().isoformat()),
|
|
342
|
+
updated=hyp_data.get("updated", datetime.now().isoformat()),
|
|
343
|
+
)
|
|
344
|
+
doc.add_hypothesis(hyp)
|
|
345
|
+
|
|
346
|
+
return doc
|
|
347
|
+
|
|
348
|
+
def __repr__(self) -> str:
|
|
349
|
+
"""Human-readable representation."""
|
|
350
|
+
return (
|
|
351
|
+
f"DiscoveryDocument("
|
|
352
|
+
f"{len(self.messages)} messages, "
|
|
353
|
+
f"{sum(len(m.signals) for m in self.messages.values())} signals, "
|
|
354
|
+
f"{len(self.hypotheses)} hypotheses)"
|
|
355
|
+
)
|