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,620 @@
|
|
|
1
|
+
"""CAN 2.0A/B protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module implements a CAN (Controller Area Network) protocol decoder
|
|
4
|
+
supporting both standard (11-bit ID) and extended (29-bit ID) frames.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.can import CANDecoder
|
|
9
|
+
>>> decoder = CANDecoder(bitrate=500000)
|
|
10
|
+
>>> for packet in decoder.decode(trace):
|
|
11
|
+
... print(f"ID: {packet.annotations['arbitration_id']:03X}")
|
|
12
|
+
... print(f"Data: {packet.data.hex()}")
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
ISO 11898-1:2015 Road vehicles - CAN - Part 1: Data link layer
|
|
16
|
+
CAN Specification Version 2.0 (Bosch, 1991)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from enum import IntEnum
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
from oscura.analyzers.protocols.base import (
|
|
28
|
+
AnnotationLevel,
|
|
29
|
+
AsyncDecoder,
|
|
30
|
+
ChannelDef,
|
|
31
|
+
DecoderState,
|
|
32
|
+
OptionDef,
|
|
33
|
+
)
|
|
34
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from collections.abc import Iterator
|
|
38
|
+
|
|
39
|
+
from numpy.typing import NDArray
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CANFrameType(IntEnum):
|
|
43
|
+
"""CAN frame types."""
|
|
44
|
+
|
|
45
|
+
DATA = 0
|
|
46
|
+
REMOTE = 1
|
|
47
|
+
ERROR = 2
|
|
48
|
+
OVERLOAD = 3
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class CANFrame:
|
|
53
|
+
"""Decoded CAN frame.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
arbitration_id: CAN ID (11-bit or 29-bit).
|
|
57
|
+
is_extended: True for 29-bit extended ID.
|
|
58
|
+
is_remote: True for remote transmission request.
|
|
59
|
+
dlc: Data length code (0-8).
|
|
60
|
+
data: Data bytes.
|
|
61
|
+
crc: Received CRC value.
|
|
62
|
+
crc_computed: Computed CRC value.
|
|
63
|
+
timestamp: Frame start time in seconds.
|
|
64
|
+
end_timestamp: Frame end time in seconds.
|
|
65
|
+
errors: List of detected errors.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
arbitration_id: int
|
|
69
|
+
is_extended: bool
|
|
70
|
+
is_remote: bool
|
|
71
|
+
dlc: int
|
|
72
|
+
data: bytes
|
|
73
|
+
crc: int
|
|
74
|
+
crc_computed: int
|
|
75
|
+
timestamp: float
|
|
76
|
+
end_timestamp: float
|
|
77
|
+
errors: list[str]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def crc_valid(self) -> bool:
|
|
81
|
+
"""Check if CRC matches."""
|
|
82
|
+
return self.crc == self.crc_computed
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class CANDecoderState(DecoderState):
|
|
86
|
+
"""State machine for CAN decoder."""
|
|
87
|
+
|
|
88
|
+
def reset(self) -> None:
|
|
89
|
+
"""Reset state."""
|
|
90
|
+
self.bit_position = 0
|
|
91
|
+
self.stuff_count = 0
|
|
92
|
+
self.last_five_bits = 0
|
|
93
|
+
self.frame_bits: list[int] = []
|
|
94
|
+
self.in_frame = False
|
|
95
|
+
self.frame_start_time = 0.0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# CAN bit timing constants
|
|
99
|
+
CAN_BITRATES = {
|
|
100
|
+
10000: "10 kbps",
|
|
101
|
+
20000: "20 kbps",
|
|
102
|
+
50000: "50 kbps",
|
|
103
|
+
100000: "100 kbps",
|
|
104
|
+
125000: "125 kbps",
|
|
105
|
+
250000: "250 kbps",
|
|
106
|
+
500000: "500 kbps",
|
|
107
|
+
800000: "800 kbps",
|
|
108
|
+
1000000: "1 Mbps",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# CRC polynomial for CAN: x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
|
|
112
|
+
CAN_CRC_POLY = 0x4599
|
|
113
|
+
CAN_CRC_INIT = 0x0000
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CANDecoder(AsyncDecoder):
|
|
117
|
+
"""CAN 2.0A/B protocol decoder.
|
|
118
|
+
|
|
119
|
+
Decodes CAN frames from digital signal captures, supporting:
|
|
120
|
+
- CAN 2.0A: Standard 11-bit identifiers
|
|
121
|
+
- CAN 2.0B: Extended 29-bit identifiers
|
|
122
|
+
- Bit stuffing detection and removal
|
|
123
|
+
- CRC checking
|
|
124
|
+
- Error detection
|
|
125
|
+
|
|
126
|
+
Attributes:
|
|
127
|
+
id: Decoder identifier.
|
|
128
|
+
name: Human-readable name.
|
|
129
|
+
channels: Required input channels.
|
|
130
|
+
options: Configurable decoder options.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
>>> decoder = CANDecoder(bitrate=500000)
|
|
134
|
+
>>> frames = list(decoder.decode(trace))
|
|
135
|
+
>>> for frame in frames:
|
|
136
|
+
... print(f"CAN ID: 0x{frame.annotations['arbitration_id']:03X}")
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
id = "can"
|
|
140
|
+
name = "CAN"
|
|
141
|
+
longname = "Controller Area Network"
|
|
142
|
+
desc = "CAN 2.0A/B bus decoder"
|
|
143
|
+
license = "MIT"
|
|
144
|
+
|
|
145
|
+
channels = [ # noqa: RUF012
|
|
146
|
+
ChannelDef("can", "CAN", "CAN bus signal (CAN_H - CAN_L or single-ended)"),
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
options = [ # noqa: RUF012
|
|
150
|
+
OptionDef(
|
|
151
|
+
"bitrate",
|
|
152
|
+
"Bit Rate",
|
|
153
|
+
"CAN bit rate in bps",
|
|
154
|
+
default=500000,
|
|
155
|
+
values=list(CAN_BITRATES.keys()),
|
|
156
|
+
),
|
|
157
|
+
OptionDef(
|
|
158
|
+
"sample_point",
|
|
159
|
+
"Sample Point",
|
|
160
|
+
"Sample point as fraction of bit time",
|
|
161
|
+
default=0.75,
|
|
162
|
+
),
|
|
163
|
+
]
|
|
164
|
+
|
|
165
|
+
def __init__( # type: ignore[no-untyped-def]
|
|
166
|
+
self,
|
|
167
|
+
bitrate: int = 500000,
|
|
168
|
+
sample_point: float = 0.75,
|
|
169
|
+
**options,
|
|
170
|
+
) -> None:
|
|
171
|
+
"""Initialize CAN decoder.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
bitrate: CAN bus bit rate in bps.
|
|
175
|
+
sample_point: Sample point as fraction of bit time (0.5-0.9).
|
|
176
|
+
**options: Additional decoder options.
|
|
177
|
+
"""
|
|
178
|
+
super().__init__(baudrate=bitrate, **options)
|
|
179
|
+
self._bitrate = bitrate
|
|
180
|
+
self._sample_point = sample_point
|
|
181
|
+
self._state = CANDecoderState()
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def bitrate(self) -> int:
|
|
185
|
+
"""Get CAN bit rate."""
|
|
186
|
+
return self._bitrate
|
|
187
|
+
|
|
188
|
+
@bitrate.setter
|
|
189
|
+
def bitrate(self, value: int) -> None:
|
|
190
|
+
"""Set CAN bit rate."""
|
|
191
|
+
self._bitrate = value
|
|
192
|
+
self._baudrate = value
|
|
193
|
+
|
|
194
|
+
def decode(
|
|
195
|
+
self,
|
|
196
|
+
trace: DigitalTrace,
|
|
197
|
+
**channels: NDArray[np.bool_],
|
|
198
|
+
) -> Iterator[ProtocolPacket]:
|
|
199
|
+
"""Decode CAN frames from digital trace.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
trace: Digital trace containing CAN signal.
|
|
203
|
+
**channels: Additional channel data (not used for single-wire CAN).
|
|
204
|
+
|
|
205
|
+
Yields:
|
|
206
|
+
ProtocolPacket for each decoded CAN frame.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
>>> decoder = CANDecoder(bitrate=500000)
|
|
210
|
+
>>> for packet in decoder.decode(trace):
|
|
211
|
+
... can_id = packet.annotations['arbitration_id']
|
|
212
|
+
... print(f"ID: 0x{can_id:03X}, Data: {packet.data.hex()}")
|
|
213
|
+
"""
|
|
214
|
+
self.reset()
|
|
215
|
+
|
|
216
|
+
data = trace.data
|
|
217
|
+
sample_rate = trace.metadata.sample_rate
|
|
218
|
+
1.0 / sample_rate
|
|
219
|
+
|
|
220
|
+
# Calculate samples per bit
|
|
221
|
+
1.0 / self._bitrate
|
|
222
|
+
samples_per_bit = round(sample_rate / self._bitrate)
|
|
223
|
+
|
|
224
|
+
if samples_per_bit < 2:
|
|
225
|
+
self.put_annotation(
|
|
226
|
+
0,
|
|
227
|
+
trace.duration,
|
|
228
|
+
AnnotationLevel.MESSAGES,
|
|
229
|
+
"Error: Sample rate too low for CAN decoding",
|
|
230
|
+
)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
# Sample offset within bit (where to sample)
|
|
234
|
+
sample_offset = int(samples_per_bit * self._sample_point)
|
|
235
|
+
|
|
236
|
+
# Find start of frames (falling edge from recessive to dominant)
|
|
237
|
+
# In CAN, recessive = 1, dominant = 0
|
|
238
|
+
frame_starts = self._find_frame_starts(data, samples_per_bit)
|
|
239
|
+
|
|
240
|
+
for frame_start_idx in frame_starts:
|
|
241
|
+
# Try to decode frame starting at this position
|
|
242
|
+
frame = self._decode_frame(
|
|
243
|
+
data,
|
|
244
|
+
frame_start_idx,
|
|
245
|
+
sample_rate,
|
|
246
|
+
samples_per_bit,
|
|
247
|
+
sample_offset,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if frame is not None:
|
|
251
|
+
# Create packet
|
|
252
|
+
packet = ProtocolPacket(
|
|
253
|
+
timestamp=frame.timestamp,
|
|
254
|
+
protocol="can",
|
|
255
|
+
data=frame.data,
|
|
256
|
+
annotations={
|
|
257
|
+
"arbitration_id": frame.arbitration_id,
|
|
258
|
+
"is_extended": frame.is_extended,
|
|
259
|
+
"is_remote": frame.is_remote,
|
|
260
|
+
"dlc": frame.dlc,
|
|
261
|
+
"crc": frame.crc,
|
|
262
|
+
"crc_valid": frame.crc_valid,
|
|
263
|
+
},
|
|
264
|
+
errors=frame.errors,
|
|
265
|
+
end_timestamp=frame.end_timestamp,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
self._packets.append(packet)
|
|
269
|
+
yield packet
|
|
270
|
+
|
|
271
|
+
def _find_frame_starts(
|
|
272
|
+
self,
|
|
273
|
+
data: NDArray[np.bool_],
|
|
274
|
+
samples_per_bit: int,
|
|
275
|
+
) -> list[int]:
|
|
276
|
+
"""Find potential frame start positions.
|
|
277
|
+
|
|
278
|
+
CAN frames start with a Start of Frame (SOF) bit, which is a
|
|
279
|
+
dominant (0) bit following bus idle (recessive/1).
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
data: Digital signal data.
|
|
283
|
+
samples_per_bit: Samples per CAN bit.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
List of sample indices for potential frame starts.
|
|
287
|
+
"""
|
|
288
|
+
frame_starts = []
|
|
289
|
+
|
|
290
|
+
# Look for falling edges (1 -> 0) after idle period
|
|
291
|
+
min_idle_bits = 3 # Minimum idle time before frame
|
|
292
|
+
min_idle_samples = min_idle_bits * samples_per_bit
|
|
293
|
+
|
|
294
|
+
i = min_idle_samples
|
|
295
|
+
while i < len(data) - samples_per_bit:
|
|
296
|
+
# Check if previous samples are mostly high (idle)
|
|
297
|
+
idle_region = data[max(0, i - min_idle_samples) : i]
|
|
298
|
+
if np.mean(idle_region) > 0.8: # Mostly recessive
|
|
299
|
+
# Check for falling edge (SOF)
|
|
300
|
+
if data[i - 1] and not data[i]:
|
|
301
|
+
frame_starts.append(i)
|
|
302
|
+
# Skip ahead to avoid detecting same frame
|
|
303
|
+
i += samples_per_bit * 20 # Skip at least 20 bits
|
|
304
|
+
continue
|
|
305
|
+
i += 1
|
|
306
|
+
|
|
307
|
+
return frame_starts
|
|
308
|
+
|
|
309
|
+
def _decode_frame(
|
|
310
|
+
self,
|
|
311
|
+
data: NDArray[np.bool_],
|
|
312
|
+
start_idx: int,
|
|
313
|
+
sample_rate: float,
|
|
314
|
+
samples_per_bit: int,
|
|
315
|
+
sample_offset: int,
|
|
316
|
+
) -> CANFrame | None:
|
|
317
|
+
"""Decode a single CAN frame.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
data: Digital signal data.
|
|
321
|
+
start_idx: Sample index of frame start (SOF).
|
|
322
|
+
sample_rate: Sample rate in Hz.
|
|
323
|
+
samples_per_bit: Samples per CAN bit.
|
|
324
|
+
sample_offset: Offset within bit for sampling.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Decoded CANFrame or None if decode fails.
|
|
328
|
+
"""
|
|
329
|
+
sample_period = 1.0 / sample_rate
|
|
330
|
+
frame_start_time = start_idx * sample_period
|
|
331
|
+
|
|
332
|
+
# Extract bits with bit stuffing removal
|
|
333
|
+
bits = [] # type: ignore[var-annotated]
|
|
334
|
+
stuff_count = 0
|
|
335
|
+
consecutive_same = 0
|
|
336
|
+
last_bit = None
|
|
337
|
+
|
|
338
|
+
bit_idx = 0
|
|
339
|
+
max_frame_bits = 150 # Maximum bits in extended frame with stuffing
|
|
340
|
+
|
|
341
|
+
current_idx = start_idx
|
|
342
|
+
|
|
343
|
+
while len(bits) < 128 and bit_idx < max_frame_bits:
|
|
344
|
+
# Calculate sample position
|
|
345
|
+
sample_pos = current_idx + sample_offset
|
|
346
|
+
|
|
347
|
+
if sample_pos >= len(data):
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
# Sample the bit
|
|
351
|
+
bit = data[sample_pos]
|
|
352
|
+
|
|
353
|
+
# Check for bit stuffing
|
|
354
|
+
if last_bit is not None:
|
|
355
|
+
if bit == last_bit:
|
|
356
|
+
consecutive_same += 1
|
|
357
|
+
else:
|
|
358
|
+
consecutive_same = 1
|
|
359
|
+
|
|
360
|
+
# After 5 consecutive same bits, next bit should be opposite (stuff bit)
|
|
361
|
+
if consecutive_same == 5:
|
|
362
|
+
# Next bit should be stuff bit - skip it
|
|
363
|
+
current_idx += samples_per_bit
|
|
364
|
+
bit_idx += 1
|
|
365
|
+
stuff_count += 1
|
|
366
|
+
|
|
367
|
+
# Sample the stuff bit to verify
|
|
368
|
+
stuff_sample_pos = current_idx + sample_offset
|
|
369
|
+
if stuff_sample_pos < len(data):
|
|
370
|
+
stuff_bit = data[stuff_sample_pos]
|
|
371
|
+
if stuff_bit == bit:
|
|
372
|
+
# Stuff error
|
|
373
|
+
pass
|
|
374
|
+
consecutive_same = 0
|
|
375
|
+
current_idx += samples_per_bit
|
|
376
|
+
bit_idx += 1
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
bits.append(int(bit))
|
|
380
|
+
last_bit = bit
|
|
381
|
+
|
|
382
|
+
current_idx += samples_per_bit
|
|
383
|
+
bit_idx += 1
|
|
384
|
+
|
|
385
|
+
if len(bits) < 20: # Minimum frame length
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
# Parse frame fields
|
|
389
|
+
frame = self._parse_frame_bits(bits, frame_start_time, sample_period, current_idx)
|
|
390
|
+
return frame
|
|
391
|
+
|
|
392
|
+
def _parse_frame_bits(
|
|
393
|
+
self,
|
|
394
|
+
bits: list[int],
|
|
395
|
+
start_time: float,
|
|
396
|
+
sample_period: float,
|
|
397
|
+
end_idx: int,
|
|
398
|
+
) -> CANFrame | None:
|
|
399
|
+
"""Parse decoded bits into CAN frame.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
bits: List of bit values (after stuff bit removal).
|
|
403
|
+
start_time: Frame start time.
|
|
404
|
+
sample_period: Sample period.
|
|
405
|
+
end_idx: End sample index.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Parsed CANFrame or None if invalid.
|
|
409
|
+
"""
|
|
410
|
+
errors = []
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
pos = 0
|
|
414
|
+
|
|
415
|
+
# SOF (should be 0)
|
|
416
|
+
if pos >= len(bits):
|
|
417
|
+
return None
|
|
418
|
+
sof = bits[pos]
|
|
419
|
+
pos += 1
|
|
420
|
+
|
|
421
|
+
if sof != 0:
|
|
422
|
+
errors.append("Invalid SOF")
|
|
423
|
+
|
|
424
|
+
# Arbitration field
|
|
425
|
+
if pos + 11 > len(bits):
|
|
426
|
+
return None
|
|
427
|
+
|
|
428
|
+
# First 11 bits of ID
|
|
429
|
+
arb_id = 0
|
|
430
|
+
for i in range(11):
|
|
431
|
+
arb_id = (arb_id << 1) | bits[pos + i]
|
|
432
|
+
pos += 11
|
|
433
|
+
|
|
434
|
+
# RTR bit (for standard) or SRR bit (for extended)
|
|
435
|
+
if pos >= len(bits):
|
|
436
|
+
return None
|
|
437
|
+
rtr_or_srr = bits[pos]
|
|
438
|
+
pos += 1
|
|
439
|
+
|
|
440
|
+
# IDE bit
|
|
441
|
+
if pos >= len(bits):
|
|
442
|
+
return None
|
|
443
|
+
ide = bits[pos]
|
|
444
|
+
pos += 1
|
|
445
|
+
|
|
446
|
+
is_extended = bool(ide)
|
|
447
|
+
is_remote = False
|
|
448
|
+
|
|
449
|
+
if is_extended:
|
|
450
|
+
# Extended frame: 18 more ID bits
|
|
451
|
+
if pos + 18 > len(bits):
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
# ID extension (18 bits)
|
|
455
|
+
for i in range(18):
|
|
456
|
+
arb_id = (arb_id << 1) | bits[pos + i]
|
|
457
|
+
pos += 18
|
|
458
|
+
|
|
459
|
+
# RTR bit
|
|
460
|
+
if pos >= len(bits):
|
|
461
|
+
return None
|
|
462
|
+
is_remote = bool(bits[pos])
|
|
463
|
+
pos += 1
|
|
464
|
+
|
|
465
|
+
# r1, r0 reserved bits
|
|
466
|
+
pos += 2
|
|
467
|
+
else:
|
|
468
|
+
# Standard frame
|
|
469
|
+
is_remote = bool(rtr_or_srr)
|
|
470
|
+
# r0 reserved bit
|
|
471
|
+
pos += 1
|
|
472
|
+
|
|
473
|
+
# DLC (4 bits)
|
|
474
|
+
if pos + 4 > len(bits):
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
dlc = 0
|
|
478
|
+
for i in range(4):
|
|
479
|
+
dlc = (dlc << 1) | bits[pos + i]
|
|
480
|
+
pos += 4
|
|
481
|
+
|
|
482
|
+
# Limit DLC to 8
|
|
483
|
+
data_len = min(dlc, 8)
|
|
484
|
+
|
|
485
|
+
# Data field (0-8 bytes)
|
|
486
|
+
if not is_remote:
|
|
487
|
+
if pos + data_len * 8 > len(bits):
|
|
488
|
+
return None
|
|
489
|
+
|
|
490
|
+
data_bytes = bytearray()
|
|
491
|
+
for byte_idx in range(data_len):
|
|
492
|
+
byte_val = 0
|
|
493
|
+
for bit_idx in range(8):
|
|
494
|
+
byte_val = (byte_val << 1) | bits[pos + byte_idx * 8 + bit_idx + bit_idx]
|
|
495
|
+
data_bytes.append(byte_val)
|
|
496
|
+
pos += 8
|
|
497
|
+
|
|
498
|
+
data = bytes(data_bytes)
|
|
499
|
+
else:
|
|
500
|
+
data = b""
|
|
501
|
+
|
|
502
|
+
# CRC field (15 bits)
|
|
503
|
+
if pos + 15 > len(bits):
|
|
504
|
+
return None
|
|
505
|
+
|
|
506
|
+
crc_received = 0
|
|
507
|
+
for i in range(15):
|
|
508
|
+
crc_received = (crc_received << 1) | bits[pos + i]
|
|
509
|
+
pos += 15
|
|
510
|
+
|
|
511
|
+
# Compute CRC on frame bits before CRC field
|
|
512
|
+
# CRC covers SOF through data field
|
|
513
|
+
crc_data_end = pos - 15
|
|
514
|
+
crc_computed = self._compute_crc(bits[:crc_data_end])
|
|
515
|
+
|
|
516
|
+
if crc_received != crc_computed:
|
|
517
|
+
errors.append(
|
|
518
|
+
f"CRC error: received 0x{crc_received:04X}, computed 0x{crc_computed:04X}"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
# CRC delimiter (should be 1)
|
|
522
|
+
if pos < len(bits) and bits[pos] != 1:
|
|
523
|
+
errors.append("CRC delimiter error")
|
|
524
|
+
pos += 1
|
|
525
|
+
|
|
526
|
+
# ACK slot and delimiter
|
|
527
|
+
pos += 2
|
|
528
|
+
|
|
529
|
+
# EOF (7 recessive bits)
|
|
530
|
+
# We don't strictly check this
|
|
531
|
+
|
|
532
|
+
end_time = start_time + pos * (1.0 / self._bitrate)
|
|
533
|
+
|
|
534
|
+
return CANFrame(
|
|
535
|
+
arbitration_id=arb_id,
|
|
536
|
+
is_extended=is_extended,
|
|
537
|
+
is_remote=is_remote,
|
|
538
|
+
dlc=dlc,
|
|
539
|
+
data=data,
|
|
540
|
+
crc=crc_received,
|
|
541
|
+
crc_computed=crc_computed,
|
|
542
|
+
timestamp=start_time,
|
|
543
|
+
end_timestamp=end_time,
|
|
544
|
+
errors=errors,
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
except (IndexError, ValueError):
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
def _compute_crc(self, bits: list[int]) -> int:
|
|
551
|
+
"""Compute CAN CRC-15.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
bits: Input bits for CRC calculation.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
15-bit CRC value.
|
|
558
|
+
"""
|
|
559
|
+
crc = CAN_CRC_INIT
|
|
560
|
+
|
|
561
|
+
for bit in bits:
|
|
562
|
+
crc_next = (crc >> 14) & 1
|
|
563
|
+
crc = (crc << 1) & 0x7FFF
|
|
564
|
+
|
|
565
|
+
if bit ^ crc_next:
|
|
566
|
+
crc ^= CAN_CRC_POLY
|
|
567
|
+
|
|
568
|
+
return crc
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def decode_can(
|
|
572
|
+
trace: DigitalTrace,
|
|
573
|
+
*,
|
|
574
|
+
bitrate: int = 500000,
|
|
575
|
+
sample_point: float = 0.75,
|
|
576
|
+
) -> list[CANFrame]:
|
|
577
|
+
"""Convenience function to decode CAN frames.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
trace: Digital trace containing CAN signal.
|
|
581
|
+
bitrate: CAN bit rate in bps (default 500000).
|
|
582
|
+
sample_point: Sample point as fraction of bit time.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
List of decoded CANFrame objects.
|
|
586
|
+
|
|
587
|
+
Example:
|
|
588
|
+
>>> frames = decode_can(trace, bitrate=500000)
|
|
589
|
+
>>> for frame in frames:
|
|
590
|
+
... print(f"ID: 0x{frame.arbitration_id:03X}")
|
|
591
|
+
"""
|
|
592
|
+
decoder = CANDecoder(bitrate=bitrate, sample_point=sample_point)
|
|
593
|
+
frames = []
|
|
594
|
+
|
|
595
|
+
for packet in decoder.decode(trace):
|
|
596
|
+
# Reconstruct CANFrame from packet
|
|
597
|
+
frame = CANFrame(
|
|
598
|
+
arbitration_id=packet.annotations["arbitration_id"],
|
|
599
|
+
is_extended=packet.annotations["is_extended"],
|
|
600
|
+
is_remote=packet.annotations["is_remote"],
|
|
601
|
+
dlc=packet.annotations["dlc"],
|
|
602
|
+
data=packet.data,
|
|
603
|
+
crc=packet.annotations["crc"],
|
|
604
|
+
crc_computed=packet.annotations["crc"], # Reconstruct as same
|
|
605
|
+
timestamp=packet.timestamp,
|
|
606
|
+
end_timestamp=packet.end_timestamp or packet.timestamp,
|
|
607
|
+
errors=packet.errors,
|
|
608
|
+
)
|
|
609
|
+
frames.append(frame)
|
|
610
|
+
|
|
611
|
+
return frames
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
__all__ = [
|
|
615
|
+
"CAN_BITRATES",
|
|
616
|
+
"CANDecoder",
|
|
617
|
+
"CANFrame",
|
|
618
|
+
"CANFrameType",
|
|
619
|
+
"decode_can",
|
|
620
|
+
]
|