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
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""Core data models for CAN bus analysis.
|
|
2
|
+
|
|
3
|
+
This module defines the fundamental data structures used throughout the
|
|
4
|
+
automotive CAN analysis subsystem.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any, Literal
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ByteAnalysis",
|
|
17
|
+
"CANMessage",
|
|
18
|
+
"CANMessageList",
|
|
19
|
+
"ChecksumInfo",
|
|
20
|
+
"CounterPattern",
|
|
21
|
+
"DecodedSignal",
|
|
22
|
+
"MessageAnalysis",
|
|
23
|
+
"SignalDefinition",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CANMessage:
|
|
29
|
+
"""A single CAN message.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
arbitration_id: CAN arbitration ID (11-bit or 29-bit).
|
|
33
|
+
timestamp: Message timestamp in seconds.
|
|
34
|
+
data: Message data bytes (0-8 bytes for CAN 2.0, up to 64 for CAN-FD).
|
|
35
|
+
is_extended: True for 29-bit extended ID.
|
|
36
|
+
is_fd: True for CAN-FD frame.
|
|
37
|
+
channel: CAN bus channel number (if available).
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
arbitration_id: int
|
|
41
|
+
timestamp: float
|
|
42
|
+
data: bytes
|
|
43
|
+
is_extended: bool = False
|
|
44
|
+
is_fd: bool = False
|
|
45
|
+
channel: int = 0
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
"""Validate message data."""
|
|
49
|
+
if not isinstance(self.data, bytes):
|
|
50
|
+
object.__setattr__(self, "data", bytes(self.data)) # type: ignore[unreachable]
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def dlc(self) -> int:
|
|
54
|
+
"""Data length code."""
|
|
55
|
+
return len(self.data)
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
"""Human-readable representation."""
|
|
59
|
+
id_str = (
|
|
60
|
+
f"0x{self.arbitration_id:03X}"
|
|
61
|
+
if not self.is_extended
|
|
62
|
+
else f"0x{self.arbitration_id:08X}"
|
|
63
|
+
)
|
|
64
|
+
data_str = self.data.hex().upper()
|
|
65
|
+
return f"CANMessage({id_str}, t={self.timestamp:.6f}s, data={data_str})"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CANMessageList:
|
|
70
|
+
"""A collection of CAN messages.
|
|
71
|
+
|
|
72
|
+
This class provides convenient operations on collections of CAN messages.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
messages: List of CAN messages.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
messages: list[CANMessage] = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
def __len__(self) -> int:
|
|
81
|
+
"""Return number of messages."""
|
|
82
|
+
return len(self.messages)
|
|
83
|
+
|
|
84
|
+
def __iter__(self) -> Iterator[CANMessage]:
|
|
85
|
+
"""Iterate over messages."""
|
|
86
|
+
return iter(self.messages)
|
|
87
|
+
|
|
88
|
+
def __getitem__(self, index: int | slice) -> CANMessage | list[CANMessage]:
|
|
89
|
+
"""Get message by index."""
|
|
90
|
+
return self.messages[index]
|
|
91
|
+
|
|
92
|
+
def append(self, message: CANMessage) -> None:
|
|
93
|
+
"""Add a message to the list."""
|
|
94
|
+
self.messages.append(message)
|
|
95
|
+
|
|
96
|
+
def filter_by_id(self, arbitration_id: int) -> CANMessageList:
|
|
97
|
+
"""Filter messages by arbitration ID.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
arbitration_id: CAN ID to filter for.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
New CANMessageList containing only messages with the specified ID.
|
|
104
|
+
"""
|
|
105
|
+
filtered = [msg for msg in self.messages if msg.arbitration_id == arbitration_id]
|
|
106
|
+
return CANMessageList(messages=filtered)
|
|
107
|
+
|
|
108
|
+
def unique_ids(self) -> set[int]:
|
|
109
|
+
"""Get set of unique arbitration IDs in this collection.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Set of unique CAN IDs.
|
|
113
|
+
"""
|
|
114
|
+
return {msg.arbitration_id for msg in self.messages}
|
|
115
|
+
|
|
116
|
+
def time_range(self) -> tuple[float, float]:
|
|
117
|
+
"""Get time range of messages.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Tuple of (first_timestamp, last_timestamp).
|
|
121
|
+
"""
|
|
122
|
+
if not self.messages:
|
|
123
|
+
return (0.0, 0.0)
|
|
124
|
+
timestamps = [msg.timestamp for msg in self.messages]
|
|
125
|
+
return (min(timestamps), max(timestamps))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class SignalDefinition:
|
|
130
|
+
"""Definition of a signal within a CAN message.
|
|
131
|
+
|
|
132
|
+
Attributes:
|
|
133
|
+
name: Signal name.
|
|
134
|
+
start_bit: Starting bit position (0-63).
|
|
135
|
+
length: Signal length in bits.
|
|
136
|
+
byte_order: Byte order ('big_endian' or 'little_endian').
|
|
137
|
+
value_type: Value type ('unsigned', 'signed', 'float').
|
|
138
|
+
scale: Scaling factor (raw_value * scale).
|
|
139
|
+
offset: Offset (scaled_value + offset).
|
|
140
|
+
unit: Physical unit (e.g., 'rpm', 'km/h', '°C').
|
|
141
|
+
min_value: Minimum valid value.
|
|
142
|
+
max_value: Maximum valid value.
|
|
143
|
+
comment: Description or notes.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
name: str
|
|
147
|
+
start_bit: int
|
|
148
|
+
length: int
|
|
149
|
+
byte_order: Literal["big_endian", "little_endian"] = "big_endian"
|
|
150
|
+
value_type: Literal["unsigned", "signed", "float"] = "unsigned"
|
|
151
|
+
scale: float = 1.0
|
|
152
|
+
offset: float = 0.0
|
|
153
|
+
unit: str = ""
|
|
154
|
+
min_value: float | None = None
|
|
155
|
+
max_value: float | None = None
|
|
156
|
+
comment: str = ""
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def start_byte(self) -> int:
|
|
160
|
+
"""Get starting byte position."""
|
|
161
|
+
return self.start_bit // 8
|
|
162
|
+
|
|
163
|
+
def extract_raw(self, data: bytes) -> int:
|
|
164
|
+
"""Extract raw value from message data.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
data: Message data bytes.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Raw integer value.
|
|
171
|
+
"""
|
|
172
|
+
# Convert bytes to bit array
|
|
173
|
+
bits = np.unpackbits(np.frombuffer(data, dtype=np.uint8))
|
|
174
|
+
|
|
175
|
+
# Extract signal bits
|
|
176
|
+
if self.byte_order == "big_endian":
|
|
177
|
+
signal_bits = bits[self.start_bit : self.start_bit + self.length]
|
|
178
|
+
else:
|
|
179
|
+
# Little endian: reverse byte order
|
|
180
|
+
byte_start = self.start_bit // 8
|
|
181
|
+
bit_offset = self.start_bit % 8
|
|
182
|
+
num_bytes = (self.length + bit_offset + 7) // 8
|
|
183
|
+
bytes_range = data[byte_start : byte_start + num_bytes]
|
|
184
|
+
reversed_bytes = bytes(reversed(bytes_range))
|
|
185
|
+
bits = np.unpackbits(np.frombuffer(reversed_bytes, dtype=np.uint8))
|
|
186
|
+
signal_bits = bits[bit_offset : bit_offset + self.length]
|
|
187
|
+
|
|
188
|
+
# Convert bits to integer
|
|
189
|
+
raw_value = int("".join(str(b) for b in signal_bits), 2)
|
|
190
|
+
|
|
191
|
+
# Handle signed values
|
|
192
|
+
if self.value_type == "signed" and raw_value >= (1 << (self.length - 1)):
|
|
193
|
+
raw_value -= 1 << self.length
|
|
194
|
+
|
|
195
|
+
return raw_value
|
|
196
|
+
|
|
197
|
+
def decode(self, data: bytes) -> float:
|
|
198
|
+
"""Decode signal value from message data.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
data: Message data bytes.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Decoded physical value.
|
|
205
|
+
"""
|
|
206
|
+
raw = self.extract_raw(data)
|
|
207
|
+
return raw * self.scale + self.offset
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class DecodedSignal:
|
|
212
|
+
"""A decoded signal value.
|
|
213
|
+
|
|
214
|
+
Attributes:
|
|
215
|
+
name: Signal name.
|
|
216
|
+
value: Decoded physical value.
|
|
217
|
+
unit: Physical unit.
|
|
218
|
+
timestamp: Message timestamp.
|
|
219
|
+
raw_value: Raw integer value before scaling.
|
|
220
|
+
definition: Signal definition used for decoding.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
name: str
|
|
224
|
+
value: float
|
|
225
|
+
unit: str
|
|
226
|
+
timestamp: float
|
|
227
|
+
raw_value: int | None = None
|
|
228
|
+
definition: SignalDefinition | None = None
|
|
229
|
+
|
|
230
|
+
def __repr__(self) -> str:
|
|
231
|
+
"""Human-readable representation."""
|
|
232
|
+
if self.unit:
|
|
233
|
+
return f"{self.name}: {self.value:.2f} {self.unit} @ {self.timestamp:.6f}s"
|
|
234
|
+
return f"{self.name}: {self.value:.2f} @ {self.timestamp:.6f}s"
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@dataclass
|
|
238
|
+
class ByteAnalysis:
|
|
239
|
+
"""Analysis results for a single byte position.
|
|
240
|
+
|
|
241
|
+
Attributes:
|
|
242
|
+
position: Byte position (0-7 for CAN 2.0).
|
|
243
|
+
entropy: Shannon entropy (0.0 = constant, higher = more variable).
|
|
244
|
+
min_value: Minimum observed value.
|
|
245
|
+
max_value: Maximum observed value.
|
|
246
|
+
mean: Mean value.
|
|
247
|
+
std: Standard deviation.
|
|
248
|
+
is_constant: True if byte never changes.
|
|
249
|
+
unique_values: Number of unique values observed.
|
|
250
|
+
most_common_value: Most frequently occurring value.
|
|
251
|
+
change_rate: Fraction of messages where value changes from previous.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
position: int
|
|
255
|
+
entropy: float
|
|
256
|
+
min_value: int
|
|
257
|
+
max_value: int
|
|
258
|
+
mean: float
|
|
259
|
+
std: float
|
|
260
|
+
is_constant: bool
|
|
261
|
+
unique_values: int
|
|
262
|
+
most_common_value: int
|
|
263
|
+
change_rate: float
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass
|
|
267
|
+
class CounterPattern:
|
|
268
|
+
"""Detected counter or sequence pattern.
|
|
269
|
+
|
|
270
|
+
Attributes:
|
|
271
|
+
byte_position: Byte position of counter.
|
|
272
|
+
bit_range: Tuple of (start_bit, length) if sub-byte counter.
|
|
273
|
+
increment: Typical increment value (usually 1).
|
|
274
|
+
wraps_at: Value where counter wraps to 0.
|
|
275
|
+
confidence: Confidence score (0.0-1.0).
|
|
276
|
+
pattern_type: Type of pattern ('counter', 'sequence', 'toggle').
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
byte_position: int
|
|
280
|
+
bit_range: tuple[int, int] | None = None
|
|
281
|
+
increment: int = 1
|
|
282
|
+
wraps_at: int = 255
|
|
283
|
+
confidence: float = 0.0
|
|
284
|
+
pattern_type: Literal["counter", "sequence", "toggle"] = "counter"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
@dataclass
|
|
288
|
+
class ChecksumInfo:
|
|
289
|
+
"""Detected checksum or CRC information.
|
|
290
|
+
|
|
291
|
+
Attributes:
|
|
292
|
+
byte_position: Byte position of checksum.
|
|
293
|
+
algorithm: Detected algorithm (e.g., 'CRC-8-SAE-J1850', 'XOR', 'SUM').
|
|
294
|
+
polynomial: CRC polynomial (if CRC).
|
|
295
|
+
covered_bytes: Byte positions covered by checksum.
|
|
296
|
+
confidence: Confidence score (0.0-1.0).
|
|
297
|
+
validation_rate: Fraction of messages with valid checksum.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
byte_position: int
|
|
301
|
+
algorithm: str
|
|
302
|
+
polynomial: int | None = None
|
|
303
|
+
covered_bytes: list[int] = field(default_factory=list)
|
|
304
|
+
confidence: float = 0.0
|
|
305
|
+
validation_rate: float = 0.0
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@dataclass
|
|
309
|
+
class MessageAnalysis:
|
|
310
|
+
"""Complete analysis of a CAN message ID.
|
|
311
|
+
|
|
312
|
+
Attributes:
|
|
313
|
+
arbitration_id: CAN arbitration ID.
|
|
314
|
+
message_count: Number of messages analyzed.
|
|
315
|
+
frequency_hz: Average message frequency in Hz.
|
|
316
|
+
period_ms: Average period in milliseconds.
|
|
317
|
+
period_jitter_ms: Period jitter (std dev) in milliseconds.
|
|
318
|
+
byte_analyses: Per-byte analysis results.
|
|
319
|
+
detected_counters: Detected counter patterns.
|
|
320
|
+
detected_checksum: Detected checksum information.
|
|
321
|
+
suggested_signals: Suggested signal boundaries.
|
|
322
|
+
correlations: Correlations with other message IDs.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
arbitration_id: int
|
|
326
|
+
message_count: int
|
|
327
|
+
frequency_hz: float
|
|
328
|
+
period_ms: float
|
|
329
|
+
period_jitter_ms: float
|
|
330
|
+
byte_analyses: list[ByteAnalysis]
|
|
331
|
+
detected_counters: list[CounterPattern] = field(default_factory=list)
|
|
332
|
+
detected_checksum: ChecksumInfo | None = None
|
|
333
|
+
suggested_signals: list[dict[str, Any]] = field(default_factory=list)
|
|
334
|
+
correlations: dict[int, float] = field(default_factory=dict)
|
|
335
|
+
|
|
336
|
+
def summary(self) -> str:
|
|
337
|
+
"""Generate human-readable summary.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
Multi-line summary string.
|
|
341
|
+
"""
|
|
342
|
+
lines = [
|
|
343
|
+
f"=== Message 0x{self.arbitration_id:03X} Analysis ===",
|
|
344
|
+
f"Count: {self.message_count} messages",
|
|
345
|
+
f"Frequency: {self.frequency_hz:.1f} Hz ({self.period_ms:.1f} ms period, jitter: {self.period_jitter_ms:.2f} ms)",
|
|
346
|
+
"",
|
|
347
|
+
"Byte Analysis:",
|
|
348
|
+
]
|
|
349
|
+
|
|
350
|
+
for ba in self.byte_analyses:
|
|
351
|
+
if ba.is_constant:
|
|
352
|
+
lines.append(f" Byte {ba.position}: CONSTANT (0x{ba.most_common_value:02X})")
|
|
353
|
+
else:
|
|
354
|
+
lines.append(
|
|
355
|
+
f" Byte {ba.position}: entropy={ba.entropy:.2f}, "
|
|
356
|
+
f"range=[0x{ba.min_value:02X}-0x{ba.max_value:02X}], "
|
|
357
|
+
f"change_rate={ba.change_rate:.2f}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
if self.detected_counters:
|
|
361
|
+
lines.append("")
|
|
362
|
+
lines.append("Detected Counters:")
|
|
363
|
+
for counter in self.detected_counters:
|
|
364
|
+
lines.append(
|
|
365
|
+
f" Byte {counter.byte_position}: {counter.pattern_type} "
|
|
366
|
+
f"(increment={counter.increment}, wraps at {counter.wraps_at}, "
|
|
367
|
+
f"confidence={counter.confidence:.2f})"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
if self.detected_checksum:
|
|
371
|
+
lines.append("")
|
|
372
|
+
lines.append("Detected Checksum:")
|
|
373
|
+
cs = self.detected_checksum
|
|
374
|
+
lines.append(
|
|
375
|
+
f" Byte {cs.byte_position}: {cs.algorithm} "
|
|
376
|
+
f"(validation_rate={cs.validation_rate:.2f}, confidence={cs.confidence:.2f})"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if self.suggested_signals:
|
|
380
|
+
lines.append("")
|
|
381
|
+
lines.append("Suggested Signal Boundaries:")
|
|
382
|
+
for sig in self.suggested_signals:
|
|
383
|
+
lines.append(f" {sig}")
|
|
384
|
+
|
|
385
|
+
return "\n".join(lines)
|