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,445 @@
|
|
|
1
|
+
"""LIN protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module provides Local Interconnect Network (LIN) automotive protocol
|
|
4
|
+
decoding for LIN 1.x and 2.x frames.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.lin import LINDecoder
|
|
9
|
+
>>> decoder = LINDecoder(baudrate=19200)
|
|
10
|
+
>>> for packet in decoder.decode(trace):
|
|
11
|
+
... print(f"ID: 0x{packet.annotations['frame_id']:02X}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
LIN Specification 1.3, 2.0, 2.1, 2.2A
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import TYPE_CHECKING, Literal
|
|
21
|
+
|
|
22
|
+
from oscura.analyzers.protocols.base import (
|
|
23
|
+
AnnotationLevel,
|
|
24
|
+
AsyncDecoder,
|
|
25
|
+
ChannelDef,
|
|
26
|
+
OptionDef,
|
|
27
|
+
)
|
|
28
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket, WaveformTrace
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from collections.abc import Iterator
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
from numpy.typing import NDArray
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class LINVersion(Enum):
|
|
38
|
+
"""LIN protocol version."""
|
|
39
|
+
|
|
40
|
+
LIN_1X = "1.x"
|
|
41
|
+
LIN_2X = "2.x"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class LINDecoder(AsyncDecoder):
|
|
45
|
+
"""LIN protocol decoder.
|
|
46
|
+
|
|
47
|
+
Decodes LIN bus frames with sync field validation, identifier extraction,
|
|
48
|
+
and checksum validation for both LIN 1.x (classic) and 2.x (enhanced).
|
|
49
|
+
|
|
50
|
+
Attributes:
|
|
51
|
+
id: "lin"
|
|
52
|
+
name: "LIN"
|
|
53
|
+
channels: [bus] (required)
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> decoder = LINDecoder(baudrate=19200, version="2.x")
|
|
57
|
+
>>> for packet in decoder.decode(trace):
|
|
58
|
+
... print(f"ID: 0x{packet.annotations['frame_id']:02X}")
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
id = "lin"
|
|
62
|
+
name = "LIN"
|
|
63
|
+
longname = "Local Interconnect Network"
|
|
64
|
+
desc = "LIN automotive bus protocol decoder"
|
|
65
|
+
|
|
66
|
+
channels = [ # noqa: RUF012
|
|
67
|
+
ChannelDef("bus", "BUS", "LIN bus signal", required=True),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
optional_channels = [] # noqa: RUF012
|
|
71
|
+
|
|
72
|
+
options = [ # noqa: RUF012
|
|
73
|
+
OptionDef(
|
|
74
|
+
"baudrate",
|
|
75
|
+
"Baud rate",
|
|
76
|
+
"Bits per second",
|
|
77
|
+
default=19200,
|
|
78
|
+
values=[9600, 19200, 20000],
|
|
79
|
+
),
|
|
80
|
+
OptionDef(
|
|
81
|
+
"version",
|
|
82
|
+
"LIN version",
|
|
83
|
+
"Protocol version",
|
|
84
|
+
default="2.x",
|
|
85
|
+
values=["1.x", "2.x"],
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
annotations = [ # noqa: RUF012
|
|
90
|
+
("sync", "Sync field"),
|
|
91
|
+
("pid", "Protected identifier"),
|
|
92
|
+
("data", "Data bytes"),
|
|
93
|
+
("checksum", "Checksum"),
|
|
94
|
+
("error", "Error"),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
baudrate: int = 19200,
|
|
100
|
+
version: Literal["1.x", "2.x"] = "2.x",
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Initialize LIN decoder.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
baudrate: Baud rate in bps (9600, 19200, 20000).
|
|
106
|
+
version: LIN version ("1.x" or "2.x").
|
|
107
|
+
"""
|
|
108
|
+
super().__init__(baudrate=baudrate, version=version)
|
|
109
|
+
self._version = LINVersion.LIN_1X if version == "1.x" else LINVersion.LIN_2X
|
|
110
|
+
|
|
111
|
+
def decode(
|
|
112
|
+
self,
|
|
113
|
+
trace: DigitalTrace | WaveformTrace,
|
|
114
|
+
**channels: NDArray[np.bool_],
|
|
115
|
+
) -> Iterator[ProtocolPacket]:
|
|
116
|
+
"""Decode LIN frames from trace.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
trace: Input digital trace.
|
|
120
|
+
**channels: Additional channel data.
|
|
121
|
+
|
|
122
|
+
Yields:
|
|
123
|
+
Decoded LIN frames as ProtocolPacket objects.
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> decoder = LINDecoder(baudrate=19200)
|
|
127
|
+
>>> for packet in decoder.decode(trace):
|
|
128
|
+
... print(f"Data: {packet.data.hex()}")
|
|
129
|
+
"""
|
|
130
|
+
# Convert to digital if needed
|
|
131
|
+
if isinstance(trace, WaveformTrace):
|
|
132
|
+
from oscura.analyzers.digital.extraction import to_digital
|
|
133
|
+
|
|
134
|
+
digital_trace = to_digital(trace, threshold="auto")
|
|
135
|
+
else:
|
|
136
|
+
digital_trace = trace
|
|
137
|
+
|
|
138
|
+
data = digital_trace.data
|
|
139
|
+
sample_rate = digital_trace.metadata.sample_rate
|
|
140
|
+
|
|
141
|
+
bit_period = sample_rate / self._baudrate
|
|
142
|
+
half_bit = bit_period / 2
|
|
143
|
+
|
|
144
|
+
idx = 0
|
|
145
|
+
frame_num = 0
|
|
146
|
+
|
|
147
|
+
while idx < len(data):
|
|
148
|
+
# Look for break field (dominant for at least 13 bit times)
|
|
149
|
+
break_start = self._find_break_field(data, idx, bit_period)
|
|
150
|
+
if break_start is None:
|
|
151
|
+
break
|
|
152
|
+
|
|
153
|
+
# After break field, find the end of the dominant period (break)
|
|
154
|
+
# and skip the delimiter (recessive) to reach the sync byte
|
|
155
|
+
sync_start_idx = break_start
|
|
156
|
+
while sync_start_idx < len(data) and not data[sync_start_idx]:
|
|
157
|
+
sync_start_idx += 1
|
|
158
|
+
|
|
159
|
+
# Skip delimiter (recessive period) to reach sync byte start bit
|
|
160
|
+
# The delimiter is at least 1 bit time recessive
|
|
161
|
+
while sync_start_idx < len(data) and data[sync_start_idx]:
|
|
162
|
+
sync_start_idx += 1
|
|
163
|
+
|
|
164
|
+
if sync_start_idx >= len(data):
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
# Sync field is 0x55 (01010101)
|
|
168
|
+
sync_byte, sync_errors = self._decode_byte(data, sync_start_idx, bit_period, half_bit)
|
|
169
|
+
|
|
170
|
+
if sync_byte != 0x55:
|
|
171
|
+
sync_errors.append(f"Invalid sync field: 0x{sync_byte:02X} (expected 0x55)")
|
|
172
|
+
|
|
173
|
+
# Protected identifier (PID)
|
|
174
|
+
pid_start_idx = int(sync_start_idx + 10 * bit_period)
|
|
175
|
+
if pid_start_idx >= len(data):
|
|
176
|
+
break
|
|
177
|
+
|
|
178
|
+
pid_byte, pid_errors = self._decode_byte(data, pid_start_idx, bit_period, half_bit)
|
|
179
|
+
|
|
180
|
+
# Extract ID and parity
|
|
181
|
+
frame_id = pid_byte & 0x3F
|
|
182
|
+
parity = (pid_byte >> 6) & 0x03
|
|
183
|
+
|
|
184
|
+
# Validate parity
|
|
185
|
+
expected_parity = self._compute_parity(frame_id)
|
|
186
|
+
if parity != expected_parity:
|
|
187
|
+
pid_errors.append(f"Parity error: {parity} (expected {expected_parity})")
|
|
188
|
+
|
|
189
|
+
# Data length depends on frame ID
|
|
190
|
+
data_length = self._get_data_length(frame_id)
|
|
191
|
+
|
|
192
|
+
# Decode data bytes
|
|
193
|
+
data_bytes = []
|
|
194
|
+
data_errors = []
|
|
195
|
+
data_start_idx = int(pid_start_idx + 10 * bit_period)
|
|
196
|
+
|
|
197
|
+
for i in range(data_length):
|
|
198
|
+
byte_start_idx = int(data_start_idx + i * 10 * bit_period)
|
|
199
|
+
if byte_start_idx >= len(data):
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
byte_val, byte_errors = self._decode_byte(
|
|
203
|
+
data, byte_start_idx, bit_period, half_bit
|
|
204
|
+
)
|
|
205
|
+
data_bytes.append(byte_val)
|
|
206
|
+
data_errors.extend(byte_errors)
|
|
207
|
+
|
|
208
|
+
# Decode checksum
|
|
209
|
+
checksum_start_idx = int(data_start_idx + data_length * 10 * bit_period)
|
|
210
|
+
if checksum_start_idx < len(data):
|
|
211
|
+
checksum_byte, checksum_errors = self._decode_byte(
|
|
212
|
+
data, checksum_start_idx, bit_period, half_bit
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Validate checksum
|
|
216
|
+
expected_checksum = self._compute_checksum(frame_id, data_bytes)
|
|
217
|
+
if checksum_byte != expected_checksum:
|
|
218
|
+
checksum_errors.append(
|
|
219
|
+
f"Checksum error: 0x{checksum_byte:02X} (expected 0x{expected_checksum:02X})"
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
checksum_byte = 0
|
|
223
|
+
checksum_errors = ["Missing checksum"]
|
|
224
|
+
|
|
225
|
+
# Calculate timestamps
|
|
226
|
+
start_time = break_start / sample_rate
|
|
227
|
+
end_time = (checksum_start_idx + 10 * bit_period) / sample_rate
|
|
228
|
+
|
|
229
|
+
# Collect all errors
|
|
230
|
+
errors = sync_errors + pid_errors + data_errors + checksum_errors
|
|
231
|
+
|
|
232
|
+
# Add annotations
|
|
233
|
+
self.put_annotation(
|
|
234
|
+
start_time,
|
|
235
|
+
end_time,
|
|
236
|
+
AnnotationLevel.PACKETS,
|
|
237
|
+
f"ID: 0x{frame_id:02X}",
|
|
238
|
+
data=bytes(data_bytes),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Create packet
|
|
242
|
+
annotations = {
|
|
243
|
+
"frame_num": frame_num,
|
|
244
|
+
"frame_id": frame_id,
|
|
245
|
+
"pid": pid_byte,
|
|
246
|
+
"data_length": data_length,
|
|
247
|
+
"checksum": checksum_byte,
|
|
248
|
+
"version": self._version.value,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
packet = ProtocolPacket(
|
|
252
|
+
timestamp=start_time,
|
|
253
|
+
protocol="lin",
|
|
254
|
+
data=bytes(data_bytes),
|
|
255
|
+
annotations=annotations,
|
|
256
|
+
errors=errors,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
yield packet
|
|
260
|
+
|
|
261
|
+
frame_num += 1
|
|
262
|
+
idx = int(checksum_start_idx + 10 * bit_period)
|
|
263
|
+
|
|
264
|
+
def _find_break_field(
|
|
265
|
+
self,
|
|
266
|
+
data: NDArray[np.bool_],
|
|
267
|
+
start_idx: int,
|
|
268
|
+
bit_period: float,
|
|
269
|
+
) -> int | None:
|
|
270
|
+
"""Find LIN break field (dominant for >= 13 bits).
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
data: Digital data array.
|
|
274
|
+
start_idx: Index to start searching.
|
|
275
|
+
bit_period: Bit period in samples.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Index of break field start, or None if not found.
|
|
279
|
+
"""
|
|
280
|
+
# Use a slightly smaller threshold to account for rounding
|
|
281
|
+
# LIN spec requires >= 13 bit times, use 12.5 to be tolerant
|
|
282
|
+
min_break_samples = int(12.5 * bit_period)
|
|
283
|
+
|
|
284
|
+
idx = start_idx
|
|
285
|
+
while idx < len(data) - min_break_samples:
|
|
286
|
+
# Look for recessive-to-dominant transition
|
|
287
|
+
if idx > 0 and data[idx - 1] and not data[idx]:
|
|
288
|
+
# Check if dominant for at least 12.5 bit periods
|
|
289
|
+
dominant_length = 0
|
|
290
|
+
check_idx = idx
|
|
291
|
+
while check_idx < len(data) and not data[check_idx]:
|
|
292
|
+
dominant_length += 1
|
|
293
|
+
check_idx += 1
|
|
294
|
+
|
|
295
|
+
if dominant_length >= min_break_samples:
|
|
296
|
+
return idx
|
|
297
|
+
|
|
298
|
+
idx += 1
|
|
299
|
+
|
|
300
|
+
return None
|
|
301
|
+
|
|
302
|
+
def _decode_byte(
|
|
303
|
+
self,
|
|
304
|
+
data: NDArray[np.bool_],
|
|
305
|
+
start_idx: int,
|
|
306
|
+
bit_period: float,
|
|
307
|
+
half_bit: float,
|
|
308
|
+
) -> tuple[int, list[str]]:
|
|
309
|
+
"""Decode UART-style byte (1 start, 8 data, 1 stop).
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
data: Digital data array.
|
|
313
|
+
start_idx: Start index (at start bit).
|
|
314
|
+
bit_period: Bit period in samples.
|
|
315
|
+
half_bit: Half bit period in samples.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
(byte_value, errors) tuple.
|
|
319
|
+
"""
|
|
320
|
+
errors = []
|
|
321
|
+
|
|
322
|
+
# Sample at center of each bit
|
|
323
|
+
sample_points = []
|
|
324
|
+
for bit_num in range(10): # Start + 8 data + stop
|
|
325
|
+
sample_idx = int(start_idx + half_bit + bit_num * bit_period)
|
|
326
|
+
if sample_idx < len(data):
|
|
327
|
+
sample_points.append(sample_idx)
|
|
328
|
+
|
|
329
|
+
if len(sample_points) < 10:
|
|
330
|
+
return 0, ["Incomplete byte"]
|
|
331
|
+
|
|
332
|
+
# Verify start bit (should be 0)
|
|
333
|
+
if data[sample_points[0]]:
|
|
334
|
+
errors.append("Invalid start bit")
|
|
335
|
+
|
|
336
|
+
# Extract data bits (LSB first)
|
|
337
|
+
byte_val = 0
|
|
338
|
+
for i in range(8):
|
|
339
|
+
bit = 1 if data[sample_points[1 + i]] else 0
|
|
340
|
+
byte_val |= bit << i
|
|
341
|
+
|
|
342
|
+
# Verify stop bit (should be 1)
|
|
343
|
+
if not data[sample_points[9]]:
|
|
344
|
+
errors.append("Framing error")
|
|
345
|
+
|
|
346
|
+
return byte_val, errors
|
|
347
|
+
|
|
348
|
+
def _compute_parity(self, frame_id: int) -> int:
|
|
349
|
+
"""Compute LIN 2.x protected identifier parity.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
frame_id: 6-bit frame identifier.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
2-bit parity value.
|
|
356
|
+
"""
|
|
357
|
+
# Extract ID bits
|
|
358
|
+
id0 = (frame_id >> 0) & 1
|
|
359
|
+
id1 = (frame_id >> 1) & 1
|
|
360
|
+
id2 = (frame_id >> 2) & 1
|
|
361
|
+
id3 = (frame_id >> 3) & 1
|
|
362
|
+
id4 = (frame_id >> 4) & 1
|
|
363
|
+
id5 = (frame_id >> 5) & 1
|
|
364
|
+
|
|
365
|
+
# P0 = ID0 ^ ID1 ^ ID2 ^ ID4
|
|
366
|
+
p0 = id0 ^ id1 ^ id2 ^ id4
|
|
367
|
+
|
|
368
|
+
# P1 = !(ID1 ^ ID3 ^ ID4 ^ ID5)
|
|
369
|
+
p1 = (id1 ^ id3 ^ id4 ^ id5) ^ 1
|
|
370
|
+
|
|
371
|
+
return (p1 << 1) | p0
|
|
372
|
+
|
|
373
|
+
def _get_data_length(self, frame_id: int) -> int:
|
|
374
|
+
"""Get data length for frame ID.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
frame_id: Frame identifier.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Data length in bytes (0-8).
|
|
381
|
+
"""
|
|
382
|
+
# Standard frame IDs have predefined lengths
|
|
383
|
+
# For simplicity, assume 8 bytes (can be configured per application)
|
|
384
|
+
return 8
|
|
385
|
+
|
|
386
|
+
def _compute_checksum(self, frame_id: int, data_bytes: list[int]) -> int:
|
|
387
|
+
"""Compute LIN checksum.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
frame_id: Frame identifier.
|
|
391
|
+
data_bytes: Data bytes.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Checksum byte.
|
|
395
|
+
"""
|
|
396
|
+
if self._version == LINVersion.LIN_1X:
|
|
397
|
+
# Classic checksum: sum of data bytes
|
|
398
|
+
checksum = sum(data_bytes)
|
|
399
|
+
else:
|
|
400
|
+
# Enhanced checksum: sum of PID + data bytes
|
|
401
|
+
pid = frame_id | (self._compute_parity(frame_id) << 6)
|
|
402
|
+
checksum = pid + sum(data_bytes)
|
|
403
|
+
|
|
404
|
+
# Handle carries
|
|
405
|
+
while checksum > 0xFF:
|
|
406
|
+
checksum = (checksum & 0xFF) + (checksum >> 8)
|
|
407
|
+
|
|
408
|
+
# Invert
|
|
409
|
+
return (~checksum) & 0xFF
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def decode_lin(
|
|
413
|
+
data: NDArray[np.bool_] | WaveformTrace | DigitalTrace,
|
|
414
|
+
sample_rate: float = 1.0,
|
|
415
|
+
baudrate: int = 19200,
|
|
416
|
+
version: Literal["1.x", "2.x"] = "2.x",
|
|
417
|
+
) -> list[ProtocolPacket]:
|
|
418
|
+
"""Convenience function to decode LIN frames.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
data: LIN bus signal (digital array or trace).
|
|
422
|
+
sample_rate: Sample rate in Hz.
|
|
423
|
+
baudrate: Baud rate (9600, 19200, 20000).
|
|
424
|
+
version: LIN version ("1.x" or "2.x").
|
|
425
|
+
|
|
426
|
+
Returns:
|
|
427
|
+
List of decoded LIN frames.
|
|
428
|
+
|
|
429
|
+
Example:
|
|
430
|
+
>>> packets = decode_lin(signal, sample_rate=1e6, baudrate=19200)
|
|
431
|
+
>>> for pkt in packets:
|
|
432
|
+
... print(f"ID: 0x{pkt.annotations['frame_id']:02X}")
|
|
433
|
+
"""
|
|
434
|
+
decoder = LINDecoder(baudrate=baudrate, version=version)
|
|
435
|
+
if isinstance(data, WaveformTrace | DigitalTrace):
|
|
436
|
+
return list(decoder.decode(data))
|
|
437
|
+
else:
|
|
438
|
+
from oscura.core.types import TraceMetadata
|
|
439
|
+
|
|
440
|
+
metadata = TraceMetadata(sample_rate=sample_rate)
|
|
441
|
+
trace = DigitalTrace(data=data, metadata=metadata)
|
|
442
|
+
return list(decoder.decode(trace))
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
__all__ = ["LINDecoder", "LINVersion", "decode_lin"]
|