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,745 @@
|
|
|
1
|
+
"""J1939 protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module implements J1939 (SAE J1939) protocol decoding for heavy-duty
|
|
4
|
+
vehicles including PGN extraction and common parameter decoding.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from oscura.automotive.can.models import CANMessage
|
|
14
|
+
|
|
15
|
+
__all__ = ["J1939Decoder", "J1939Message", "extract_pgn"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class J1939Message:
|
|
20
|
+
"""Decoded J1939 message.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
pgn: Parameter Group Number.
|
|
24
|
+
priority: Message priority (0-7).
|
|
25
|
+
source_address: Source address.
|
|
26
|
+
destination_address: Destination address (0xFF for broadcast).
|
|
27
|
+
data: Message data bytes.
|
|
28
|
+
timestamp: Message timestamp.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
pgn: int
|
|
32
|
+
priority: int
|
|
33
|
+
source_address: int
|
|
34
|
+
destination_address: int
|
|
35
|
+
data: bytes
|
|
36
|
+
timestamp: float
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def extract_pgn(can_id: int) -> tuple[int, int, int, int]:
|
|
40
|
+
"""Extract J1939 components from 29-bit CAN ID.
|
|
41
|
+
|
|
42
|
+
J1939 uses extended 29-bit CAN IDs with this structure:
|
|
43
|
+
- Priority (bits 26-28): 3 bits
|
|
44
|
+
- Reserved (bit 25): 1 bit
|
|
45
|
+
- Data Page (bit 24): 1 bit
|
|
46
|
+
- PDU Format (bits 16-23): 8 bits
|
|
47
|
+
- PDU Specific (bits 8-15): 8 bits
|
|
48
|
+
- Source Address (bits 0-7): 8 bits
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
can_id: 29-bit extended CAN ID.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Tuple of (pgn, priority, destination_address, source_address).
|
|
55
|
+
"""
|
|
56
|
+
# Extract components
|
|
57
|
+
priority = (can_id >> 26) & 0x7
|
|
58
|
+
# reserved = (can_id >> 25) & 0x1 # Not used in PGN calculation
|
|
59
|
+
data_page = (can_id >> 24) & 0x1
|
|
60
|
+
pdu_format = (can_id >> 16) & 0xFF
|
|
61
|
+
pdu_specific = (can_id >> 8) & 0xFF
|
|
62
|
+
source_address = can_id & 0xFF
|
|
63
|
+
|
|
64
|
+
# Calculate PGN
|
|
65
|
+
# If PDU Format < 240, PDU Specific is Destination Address
|
|
66
|
+
# If PDU Format >= 240, PDU Specific is Group Extension
|
|
67
|
+
if pdu_format < 240:
|
|
68
|
+
# PDU1 format - destination-specific
|
|
69
|
+
pgn = (data_page << 16) | (pdu_format << 8)
|
|
70
|
+
destination_address = pdu_specific
|
|
71
|
+
else:
|
|
72
|
+
# PDU2 format - broadcast
|
|
73
|
+
pgn = (data_page << 16) | (pdu_format << 8) | pdu_specific
|
|
74
|
+
destination_address = 0xFF # Broadcast
|
|
75
|
+
|
|
76
|
+
return pgn, priority, destination_address, source_address
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class J1939Decoder:
|
|
80
|
+
"""J1939 protocol decoder.
|
|
81
|
+
|
|
82
|
+
Decodes J1939 messages from extended CAN frames with support for
|
|
83
|
+
100+ Parameter Group Numbers (PGNs) and signal extraction.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
# Comprehensive PGN names (100+ common PGNs)
|
|
87
|
+
PGN_NAMES: ClassVar[dict[int, str]] = {
|
|
88
|
+
# Engine Parameters - Core (61440-61695, 0xF000-0xF0FF)
|
|
89
|
+
0xF000: "Electronic Retarder Controller 1 (ERC1)",
|
|
90
|
+
0xF001: "Electronic Brake Controller 1 (EBC1)",
|
|
91
|
+
0xF002: "Electronic Transmission Controller 1 (ETC1)",
|
|
92
|
+
0xF003: "Electronic Engine Controller 2 (EEC2)",
|
|
93
|
+
0xF004: "Electronic Engine Controller 1 (EEC1)",
|
|
94
|
+
0xF005: "Electronic Transmission Controller 2 (ETC2)",
|
|
95
|
+
0xF009: "Vehicle Dynamic Stability Control 2 (VDC2)",
|
|
96
|
+
0xF010: "Aftertreatment 1 Intake Gas 1 (AT1IG1)",
|
|
97
|
+
0xF011: "Aftertreatment 1 Outlet Gas 1 (AT1OG1)",
|
|
98
|
+
# Engine Fluid Levels & Temperatures (65248-65279, 0xFEE0-0xFEFF)
|
|
99
|
+
0xFEE0: "Electronic Engine Controller 3 (EEC3)",
|
|
100
|
+
0xFEE5: "Engine Hours, Revolutions",
|
|
101
|
+
0xFEE6: "Time/Date",
|
|
102
|
+
0xFEE7: "Vehicle Hours",
|
|
103
|
+
0xFEE8: "Vehicle Direction/Speed",
|
|
104
|
+
0xFEE9: "Vehicle Weight",
|
|
105
|
+
0xFEEA: "Vehicle Identification",
|
|
106
|
+
0xFEEB: "Component Identification",
|
|
107
|
+
0xFEEC: "Vehicle Distance",
|
|
108
|
+
0xFEED: "Shutdown",
|
|
109
|
+
0xFEEE: "Engine Temperature 1",
|
|
110
|
+
0xFEEF: "Engine Fluid Level/Pressure 1",
|
|
111
|
+
0xFEF0: "Power Takeoff Information",
|
|
112
|
+
0xFEF1: "Cruise Control/Vehicle Speed 1 (CCVS1)",
|
|
113
|
+
0xFEF2: "Fuel Economy (Liquid)",
|
|
114
|
+
0xFEF3: "Engine Configuration 1",
|
|
115
|
+
0xFEF4: "Fuel Consumption (Gaseous)",
|
|
116
|
+
0xFEF5: "Ambient Conditions",
|
|
117
|
+
0xFEF6: "Inlet/Exhaust Conditions 1",
|
|
118
|
+
0xFEF7: "Vehicle Electrical Power 1",
|
|
119
|
+
0xFEF8: "Transmission Fluids 1",
|
|
120
|
+
0xFEF9: "Air Supply Pressure",
|
|
121
|
+
0xFEFA: "Vehicle Weight",
|
|
122
|
+
0xFEFB: "Engine Speed/Load Factor",
|
|
123
|
+
0xFEFC: "Fan Drive",
|
|
124
|
+
0xFEFD: "Vehicle Position",
|
|
125
|
+
0xFEFE: "Engine Temperature 2",
|
|
126
|
+
0xFEFF: "Electronic Engine Controller 4 (EEC4)",
|
|
127
|
+
# Transmission & Drivetrain (65184-65247, 0xFEC0-0xFEDF)
|
|
128
|
+
0xFEC0: "Transmission Configuration",
|
|
129
|
+
0xFEC1: "High Resolution Vehicle Distance",
|
|
130
|
+
0xFEC2: "High Resolution Fuel Consumption (Liquid)",
|
|
131
|
+
0xFEC3: "High Resolution Fuel Economy (Liquid)",
|
|
132
|
+
0xFEC5: "Aftertreatment 1 Diesel Exhaust Fluid Tank 1 Info",
|
|
133
|
+
0xFEC6: "Aftertreatment 1 Diesel Oxidation Catalyst 1",
|
|
134
|
+
0xFEC7: "Aftertreatment 1 Diesel Particulate Filter 1",
|
|
135
|
+
0xFEC8: "Aftertreatment 1 Intake Gas",
|
|
136
|
+
0xFEC9: "Aftertreatment 1 Outlet Gas",
|
|
137
|
+
0xFECA: "DM1 - Active Diagnostic Trouble Codes",
|
|
138
|
+
0xFECB: "DM2 - Previously Active Diagnostic Trouble Codes",
|
|
139
|
+
0xFECC: "DM3 - Diagnostic Data Clear/Reset Previously Active DTCs",
|
|
140
|
+
0xFECD: "DM4 - Freeze Frame Parameters",
|
|
141
|
+
0xFECE: "DM5 - Diagnostic Readiness 1",
|
|
142
|
+
0xFECF: "DM6 - Emission-Related Pending DTCs",
|
|
143
|
+
0xFED0: "DM7 - Command Non-Continuously Monitored Test",
|
|
144
|
+
0xFED1: "DM8 - Test Results",
|
|
145
|
+
0xFED2: "DM9 - Request Test Results",
|
|
146
|
+
0xFED3: "DM11 - Diagnostic Data Clear/Reset Active DTCs",
|
|
147
|
+
0xFED4: "DM12 - Emissions-Related Active DTCs",
|
|
148
|
+
0xFED5: "DM13 - Stop Start Broadcast",
|
|
149
|
+
0xFED6: "DM14 - Memory Access Response",
|
|
150
|
+
0xFED7: "DM15 - Memory Access",
|
|
151
|
+
0xFED8: "DM16 - Binary Data Transfer",
|
|
152
|
+
0xFED9: "DM17 - Binary Data Transfer Response",
|
|
153
|
+
0xFEDA: "DM18 - Binary Data Transfer",
|
|
154
|
+
0xFEDB: "DM19 - Calibration Information",
|
|
155
|
+
0xFEDC: "DM20 - Monitor Performance Ratio",
|
|
156
|
+
0xFEDD: "DM21 - Diagnostic Readiness 2",
|
|
157
|
+
0xFEDE: "DM22 - Individual Clear/Reset Active & Previously Active DTC",
|
|
158
|
+
0xFEDF: "DM23 - Emission-Related Previously Active DTC",
|
|
159
|
+
# Brake & Wheels (65120-65183, 0xFE80-0xFEBF)
|
|
160
|
+
0xFE80: "Tire Condition",
|
|
161
|
+
0xFE81: "Tire Pressure",
|
|
162
|
+
0xFE82: "Tire Temperature",
|
|
163
|
+
0xFE83: "Tire Pressure Control Unit",
|
|
164
|
+
0xFE90: "Hydraulic Pressure 1",
|
|
165
|
+
0xFE91: "Hydraulic Pressure 2",
|
|
166
|
+
0xFE92: "Fuel Consumption (Liquid) 1",
|
|
167
|
+
0xFE93: "Fuel Economy (Liquid) 1",
|
|
168
|
+
0xFEA0: "Axle Information",
|
|
169
|
+
0xFEA1: "Engine Torque/Speed",
|
|
170
|
+
0xFEA2: "Electronic Transmission Controller 3 (ETC3)",
|
|
171
|
+
0xFEA3: "Electronic Transmission Controller 4 (ETC4)",
|
|
172
|
+
0xFEA4: "Electronic Transmission Controller 5 (ETC5)",
|
|
173
|
+
0xFEA5: "Electronic Engine Controller 5 (EEC5)",
|
|
174
|
+
0xFEA6: "Electronic Engine Controller 6 (EEC6)",
|
|
175
|
+
0xFEA7: "Electronic Engine Controller 7 (EEC7)",
|
|
176
|
+
0xFEB0: "Axle Weight",
|
|
177
|
+
0xFEB1: "Trailer Weight",
|
|
178
|
+
0xFEB2: "Cargo Weight",
|
|
179
|
+
0xFEB3: "Trip Fuel Economy (Liquid)",
|
|
180
|
+
0xFEB4: "Trip Fuel (Liquid)",
|
|
181
|
+
0xFEB5: "Trip Time",
|
|
182
|
+
0xFEB6: "Trip Shutdown Information",
|
|
183
|
+
0xFEB7: "Fuel Level 1",
|
|
184
|
+
0xFEB8: "Fuel Level 2",
|
|
185
|
+
0xFEB9: "Auxiliary Water Pump Pressure",
|
|
186
|
+
0xFEBA: "Coolant Filter Differential Pressure",
|
|
187
|
+
0xFEBB: "Engine Exhaust Gas Recirculation 1 (EGR1)",
|
|
188
|
+
0xFEBC: "Engine Exhaust Gas Recirculation 2 (EGR2)",
|
|
189
|
+
0xFEBD: "Engine Exhaust Gas Recirculation 3 (EGR3)",
|
|
190
|
+
0xFEBE: "Aftertreatment 1 Diesel Particulate Filter 2",
|
|
191
|
+
0xFEBF: "Wheel Speed Information",
|
|
192
|
+
# Aftertreatment & Emissions (65024-65119, 0xFE40-0xFE7F)
|
|
193
|
+
0xFE40: "Aftertreatment 1 SCR Exhaust Gas Temperature 1",
|
|
194
|
+
0xFE41: "Aftertreatment 1 SCR Dosing System Information 1",
|
|
195
|
+
0xFE42: "Aftertreatment 1 Intake NOx",
|
|
196
|
+
0xFE43: "Aftertreatment 1 Outlet NOx",
|
|
197
|
+
0xFE44: "Aftertreatment 2 Intake Gas 1",
|
|
198
|
+
0xFE45: "Aftertreatment 2 Outlet Gas 1",
|
|
199
|
+
0xFE46: "Aftertreatment 2 SCR Exhaust Gas Temperature 1",
|
|
200
|
+
0xFE47: "Aftertreatment 2 SCR Dosing System Information 1",
|
|
201
|
+
0xFE48: "Aftertreatment 2 Intake NOx",
|
|
202
|
+
0xFE49: "Aftertreatment 2 Outlet NOx",
|
|
203
|
+
0xFE4A: "Fuel Information 1 (Liquid)",
|
|
204
|
+
0xFE4B: "Fuel Information 2 (Liquid)",
|
|
205
|
+
0xFE4C: "Fuel Information 3 (Liquid)",
|
|
206
|
+
0xFE4D: "Engine Gas Flow Rate",
|
|
207
|
+
0xFE4E: "Engine Throttle Valve 1",
|
|
208
|
+
0xFE4F: "Engine Throttle Valve 2",
|
|
209
|
+
0xFE50: "Aftertreatment 1 Diesel Particulate Filter 3",
|
|
210
|
+
0xFE51: "Aftertreatment 1 Diesel Particulate Filter 4",
|
|
211
|
+
0xFE52: "Aftertreatment 1 SCR Dosing System Requests",
|
|
212
|
+
0xFE53: "Aftertreatment 1 Fuel Control 1",
|
|
213
|
+
0xFE54: "Aftertreatment 1 Fuel Control 2",
|
|
214
|
+
0xFE55: "Aftertreatment 2 Diesel Particulate Filter 1",
|
|
215
|
+
0xFE56: "Aftertreatment 1 Diesel Exhaust Fluid Tank 1 Information",
|
|
216
|
+
0xFE57: "Aftertreatment 2 Diesel Exhaust Fluid Tank 1 Information",
|
|
217
|
+
0xFE58: "Fuel Information (Gaseous)",
|
|
218
|
+
0xFE59: "Aftertreatment 1 Air Control 1",
|
|
219
|
+
0xFE5A: "Aftertreatment 2 Air Control 1",
|
|
220
|
+
0xFE5B: "Aftertreatment 1 Diesel Particulate Filter Control 1",
|
|
221
|
+
0xFE5C: "Aftertreatment 2 Diesel Particulate Filter Control 1",
|
|
222
|
+
# Cab Climate & Lighting (64256-64511, 0xFB00-0xFBFF)
|
|
223
|
+
0xFB00: "Cab Climate Control Status 1",
|
|
224
|
+
0xFB01: "Cab Climate Control Status 2",
|
|
225
|
+
0xFB02: "Cab Climate Control Command 1",
|
|
226
|
+
0xFB03: "Cab Climate Control Command 2",
|
|
227
|
+
# Additional Common PGNs
|
|
228
|
+
0xC100: "DM1 - Active Diagnostic Trouble Codes (Request)",
|
|
229
|
+
0xC200: "DM13 - Stop Start Broadcast (Request)",
|
|
230
|
+
0xC300: "DM2 - Previously Active DTCs (Request)",
|
|
231
|
+
0xFF00: "Aftertreatment 1 Diesel Particulate Filter Control",
|
|
232
|
+
# Proprietary PGNs (common ranges)
|
|
233
|
+
61184: "Electronic Retarder Controller 1 (ERC1)",
|
|
234
|
+
61185: "Electronic Brake Controller 2 (EBC2)",
|
|
235
|
+
61186: "Electronic Transmission Controller 1 (ETC1)",
|
|
236
|
+
61187: "Electronic Engine Controller 2 (EEC2)",
|
|
237
|
+
61188: "Electronic Engine Controller 1 (EEC1)",
|
|
238
|
+
61189: "Electronic Transmission Controller 2 (ETC2)",
|
|
239
|
+
61190: "Turbocharger 1",
|
|
240
|
+
61191: "Turbocharger 2",
|
|
241
|
+
61192: "Air Intake Conditions",
|
|
242
|
+
61193: "Exhaust Gas Recirculation",
|
|
243
|
+
61194: "Fuel System",
|
|
244
|
+
61195: "Alternator Information",
|
|
245
|
+
61196: "Intake Manifold Temperature 1",
|
|
246
|
+
61197: "Exhaust Port Temperature",
|
|
247
|
+
61198: "Engine Oil Information",
|
|
248
|
+
61199: "Engine Coolant Information",
|
|
249
|
+
61200: "Fuel Delivery Information",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# Signal definitions for common PGNs
|
|
253
|
+
# Format: {pgn: {signal_name: {start_byte, bit_offset, length_bits, scale, offset, unit}}}
|
|
254
|
+
PGN_SIGNALS: ClassVar[dict[int, dict[str, dict[str, int | float | str]]]] = {
|
|
255
|
+
0xF004: { # Electronic Engine Controller 1 (EEC1)
|
|
256
|
+
"engine_torque_mode": {
|
|
257
|
+
"byte": 0,
|
|
258
|
+
"bit": 0,
|
|
259
|
+
"length": 4,
|
|
260
|
+
"scale": 1,
|
|
261
|
+
"offset": 0,
|
|
262
|
+
"unit": "",
|
|
263
|
+
},
|
|
264
|
+
"driver_demand_torque": {
|
|
265
|
+
"byte": 1,
|
|
266
|
+
"bit": 0,
|
|
267
|
+
"length": 8,
|
|
268
|
+
"scale": 1,
|
|
269
|
+
"offset": -125,
|
|
270
|
+
"unit": "%",
|
|
271
|
+
},
|
|
272
|
+
"actual_engine_torque": {
|
|
273
|
+
"byte": 2,
|
|
274
|
+
"bit": 0,
|
|
275
|
+
"length": 8,
|
|
276
|
+
"scale": 1,
|
|
277
|
+
"offset": -125,
|
|
278
|
+
"unit": "%",
|
|
279
|
+
},
|
|
280
|
+
"engine_speed": {
|
|
281
|
+
"byte": 3,
|
|
282
|
+
"bit": 0,
|
|
283
|
+
"length": 16,
|
|
284
|
+
"scale": 0.125,
|
|
285
|
+
"offset": 0,
|
|
286
|
+
"unit": "rpm",
|
|
287
|
+
},
|
|
288
|
+
"source_address": {
|
|
289
|
+
"byte": 5,
|
|
290
|
+
"bit": 0,
|
|
291
|
+
"length": 8,
|
|
292
|
+
"scale": 1,
|
|
293
|
+
"offset": 0,
|
|
294
|
+
"unit": "",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
0xF003: { # Electronic Engine Controller 2 (EEC2)
|
|
298
|
+
"accelerator_pedal_position": {
|
|
299
|
+
"byte": 1,
|
|
300
|
+
"bit": 0,
|
|
301
|
+
"length": 8,
|
|
302
|
+
"scale": 0.4,
|
|
303
|
+
"offset": 0,
|
|
304
|
+
"unit": "%",
|
|
305
|
+
},
|
|
306
|
+
"engine_percent_load": {
|
|
307
|
+
"byte": 2,
|
|
308
|
+
"bit": 0,
|
|
309
|
+
"length": 8,
|
|
310
|
+
"scale": 1,
|
|
311
|
+
"offset": 0,
|
|
312
|
+
"unit": "%",
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
0xFEF1: { # Cruise Control/Vehicle Speed 1
|
|
316
|
+
"wheel_based_speed": {
|
|
317
|
+
"byte": 1,
|
|
318
|
+
"bit": 0,
|
|
319
|
+
"length": 16,
|
|
320
|
+
"scale": 1 / 256,
|
|
321
|
+
"offset": 0,
|
|
322
|
+
"unit": "km/h",
|
|
323
|
+
},
|
|
324
|
+
"cruise_control_active": {
|
|
325
|
+
"byte": 3,
|
|
326
|
+
"bit": 0,
|
|
327
|
+
"length": 2,
|
|
328
|
+
"scale": 1,
|
|
329
|
+
"offset": 0,
|
|
330
|
+
"unit": "",
|
|
331
|
+
},
|
|
332
|
+
"brake_switch": {
|
|
333
|
+
"byte": 3,
|
|
334
|
+
"bit": 2,
|
|
335
|
+
"length": 2,
|
|
336
|
+
"scale": 1,
|
|
337
|
+
"offset": 0,
|
|
338
|
+
"unit": "",
|
|
339
|
+
},
|
|
340
|
+
"clutch_switch": {
|
|
341
|
+
"byte": 3,
|
|
342
|
+
"bit": 4,
|
|
343
|
+
"length": 2,
|
|
344
|
+
"scale": 1,
|
|
345
|
+
"offset": 0,
|
|
346
|
+
"unit": "",
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
0xFEEE: { # Engine Temperature 1
|
|
350
|
+
"coolant_temperature": {
|
|
351
|
+
"byte": 0,
|
|
352
|
+
"bit": 0,
|
|
353
|
+
"length": 8,
|
|
354
|
+
"scale": 1,
|
|
355
|
+
"offset": -40,
|
|
356
|
+
"unit": "°C",
|
|
357
|
+
},
|
|
358
|
+
"fuel_temperature": {
|
|
359
|
+
"byte": 1,
|
|
360
|
+
"bit": 0,
|
|
361
|
+
"length": 8,
|
|
362
|
+
"scale": 1,
|
|
363
|
+
"offset": -40,
|
|
364
|
+
"unit": "°C",
|
|
365
|
+
},
|
|
366
|
+
"oil_temperature": {
|
|
367
|
+
"byte": 2,
|
|
368
|
+
"bit": 0,
|
|
369
|
+
"length": 16,
|
|
370
|
+
"scale": 0.03125,
|
|
371
|
+
"offset": -273,
|
|
372
|
+
"unit": "°C",
|
|
373
|
+
},
|
|
374
|
+
"turbo_oil_temperature": {
|
|
375
|
+
"byte": 4,
|
|
376
|
+
"bit": 0,
|
|
377
|
+
"length": 16,
|
|
378
|
+
"scale": 0.03125,
|
|
379
|
+
"offset": -273,
|
|
380
|
+
"unit": "°C",
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
0xFEEF: { # Engine Fluid Level/Pressure 1
|
|
384
|
+
"fuel_delivery_pressure": {
|
|
385
|
+
"byte": 0,
|
|
386
|
+
"bit": 0,
|
|
387
|
+
"length": 8,
|
|
388
|
+
"scale": 4,
|
|
389
|
+
"offset": 0,
|
|
390
|
+
"unit": "kPa",
|
|
391
|
+
},
|
|
392
|
+
"oil_pressure": {
|
|
393
|
+
"byte": 3,
|
|
394
|
+
"bit": 0,
|
|
395
|
+
"length": 8,
|
|
396
|
+
"scale": 4,
|
|
397
|
+
"offset": 0,
|
|
398
|
+
"unit": "kPa",
|
|
399
|
+
},
|
|
400
|
+
"crankcase_pressure": {
|
|
401
|
+
"byte": 5,
|
|
402
|
+
"bit": 0,
|
|
403
|
+
"length": 16,
|
|
404
|
+
"scale": 0.125,
|
|
405
|
+
"offset": -250,
|
|
406
|
+
"unit": "kPa",
|
|
407
|
+
},
|
|
408
|
+
"coolant_pressure": {
|
|
409
|
+
"byte": 7,
|
|
410
|
+
"bit": 0,
|
|
411
|
+
"length": 8,
|
|
412
|
+
"scale": 2,
|
|
413
|
+
"offset": 0,
|
|
414
|
+
"unit": "kPa",
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
0xFEF2: { # Fuel Economy (Liquid)
|
|
418
|
+
"fuel_rate": {
|
|
419
|
+
"byte": 0,
|
|
420
|
+
"bit": 0,
|
|
421
|
+
"length": 16,
|
|
422
|
+
"scale": 0.05,
|
|
423
|
+
"offset": 0,
|
|
424
|
+
"unit": "L/h",
|
|
425
|
+
},
|
|
426
|
+
"instantaneous_fuel_economy": {
|
|
427
|
+
"byte": 2,
|
|
428
|
+
"bit": 0,
|
|
429
|
+
"length": 16,
|
|
430
|
+
"scale": 1 / 512,
|
|
431
|
+
"offset": 0,
|
|
432
|
+
"unit": "km/L",
|
|
433
|
+
},
|
|
434
|
+
"average_fuel_economy": {
|
|
435
|
+
"byte": 4,
|
|
436
|
+
"bit": 0,
|
|
437
|
+
"length": 16,
|
|
438
|
+
"scale": 1 / 512,
|
|
439
|
+
"offset": 0,
|
|
440
|
+
"unit": "km/L",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
0xFEF5: { # Ambient Conditions
|
|
444
|
+
"barometric_pressure": {
|
|
445
|
+
"byte": 0,
|
|
446
|
+
"bit": 0,
|
|
447
|
+
"length": 8,
|
|
448
|
+
"scale": 0.5,
|
|
449
|
+
"offset": 0,
|
|
450
|
+
"unit": "kPa",
|
|
451
|
+
},
|
|
452
|
+
"ambient_air_temperature": {
|
|
453
|
+
"byte": 3,
|
|
454
|
+
"bit": 0,
|
|
455
|
+
"length": 16,
|
|
456
|
+
"scale": 0.03125,
|
|
457
|
+
"offset": -273,
|
|
458
|
+
"unit": "°C",
|
|
459
|
+
},
|
|
460
|
+
"ambient_air_humidity": {
|
|
461
|
+
"byte": 6,
|
|
462
|
+
"bit": 0,
|
|
463
|
+
"length": 8,
|
|
464
|
+
"scale": 0.4,
|
|
465
|
+
"offset": 0,
|
|
466
|
+
"unit": "%",
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
0xFEF7: { # Vehicle Electrical Power 1
|
|
470
|
+
"battery_potential": {
|
|
471
|
+
"byte": 4,
|
|
472
|
+
"bit": 0,
|
|
473
|
+
"length": 16,
|
|
474
|
+
"scale": 0.05,
|
|
475
|
+
"offset": 0,
|
|
476
|
+
"unit": "V",
|
|
477
|
+
},
|
|
478
|
+
"alternator_current": {
|
|
479
|
+
"byte": 6,
|
|
480
|
+
"bit": 0,
|
|
481
|
+
"length": 16,
|
|
482
|
+
"scale": 1,
|
|
483
|
+
"offset": -125,
|
|
484
|
+
"unit": "A",
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
0xFEE5: { # Engine Hours, Revolutions
|
|
488
|
+
"total_engine_hours": {
|
|
489
|
+
"byte": 0,
|
|
490
|
+
"bit": 0,
|
|
491
|
+
"length": 32,
|
|
492
|
+
"scale": 0.05,
|
|
493
|
+
"offset": 0,
|
|
494
|
+
"unit": "hours",
|
|
495
|
+
},
|
|
496
|
+
"total_engine_revolutions": {
|
|
497
|
+
"byte": 4,
|
|
498
|
+
"bit": 0,
|
|
499
|
+
"length": 32,
|
|
500
|
+
"scale": 1000,
|
|
501
|
+
"offset": 0,
|
|
502
|
+
"unit": "revolutions",
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
0xFECA: { # DM1 - Active Diagnostic Trouble Codes
|
|
506
|
+
"lamp_status_malfunction_indicator": {
|
|
507
|
+
"byte": 0,
|
|
508
|
+
"bit": 0,
|
|
509
|
+
"length": 2,
|
|
510
|
+
"scale": 1,
|
|
511
|
+
"offset": 0,
|
|
512
|
+
"unit": "",
|
|
513
|
+
},
|
|
514
|
+
"lamp_status_red_stop": {
|
|
515
|
+
"byte": 0,
|
|
516
|
+
"bit": 2,
|
|
517
|
+
"length": 2,
|
|
518
|
+
"scale": 1,
|
|
519
|
+
"offset": 0,
|
|
520
|
+
"unit": "",
|
|
521
|
+
},
|
|
522
|
+
"lamp_status_amber_warning": {
|
|
523
|
+
"byte": 0,
|
|
524
|
+
"bit": 4,
|
|
525
|
+
"length": 2,
|
|
526
|
+
"scale": 1,
|
|
527
|
+
"offset": 0,
|
|
528
|
+
"unit": "",
|
|
529
|
+
},
|
|
530
|
+
"lamp_status_protect": {
|
|
531
|
+
"byte": 0,
|
|
532
|
+
"bit": 6,
|
|
533
|
+
"length": 2,
|
|
534
|
+
"scale": 1,
|
|
535
|
+
"offset": 0,
|
|
536
|
+
"unit": "",
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
0xFEC1: { # High Resolution Vehicle Distance
|
|
540
|
+
"high_resolution_total_distance": {
|
|
541
|
+
"byte": 0,
|
|
542
|
+
"bit": 0,
|
|
543
|
+
"length": 32,
|
|
544
|
+
"scale": 5,
|
|
545
|
+
"offset": 0,
|
|
546
|
+
"unit": "m",
|
|
547
|
+
},
|
|
548
|
+
"high_resolution_trip_distance": {
|
|
549
|
+
"byte": 4,
|
|
550
|
+
"bit": 0,
|
|
551
|
+
"length": 32,
|
|
552
|
+
"scale": 5,
|
|
553
|
+
"offset": 0,
|
|
554
|
+
"unit": "m",
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
0xFEBF: { # Wheel Speed Information
|
|
558
|
+
"front_axle_speed": {
|
|
559
|
+
"byte": 0,
|
|
560
|
+
"bit": 0,
|
|
561
|
+
"length": 16,
|
|
562
|
+
"scale": 1 / 256,
|
|
563
|
+
"offset": 0,
|
|
564
|
+
"unit": "km/h",
|
|
565
|
+
},
|
|
566
|
+
"rear_axle_1_speed": {
|
|
567
|
+
"byte": 2,
|
|
568
|
+
"bit": 0,
|
|
569
|
+
"length": 16,
|
|
570
|
+
"scale": 1 / 256,
|
|
571
|
+
"offset": 0,
|
|
572
|
+
"unit": "km/h",
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
@staticmethod
|
|
578
|
+
def is_j1939(message: CANMessage) -> bool:
|
|
579
|
+
"""Check if message uses J1939 protocol.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
message: CAN message to check.
|
|
583
|
+
|
|
584
|
+
Returns:
|
|
585
|
+
True if message appears to be J1939 (extended ID).
|
|
586
|
+
"""
|
|
587
|
+
return message.is_extended
|
|
588
|
+
|
|
589
|
+
@staticmethod
|
|
590
|
+
def decode(message: CANMessage) -> J1939Message:
|
|
591
|
+
"""Decode J1939 message from CAN frame.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
message: CAN message with extended ID.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
J1939Message with decoded components.
|
|
598
|
+
|
|
599
|
+
Raises:
|
|
600
|
+
ValueError: If message is not extended frame.
|
|
601
|
+
"""
|
|
602
|
+
if not message.is_extended:
|
|
603
|
+
raise ValueError("J1939 requires extended (29-bit) CAN ID")
|
|
604
|
+
|
|
605
|
+
pgn, priority, dest_addr, src_addr = extract_pgn(message.arbitration_id)
|
|
606
|
+
|
|
607
|
+
return J1939Message(
|
|
608
|
+
pgn=pgn,
|
|
609
|
+
priority=priority,
|
|
610
|
+
source_address=src_addr,
|
|
611
|
+
destination_address=dest_addr,
|
|
612
|
+
data=message.data,
|
|
613
|
+
timestamp=message.timestamp,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
@staticmethod
|
|
617
|
+
def get_pgn_name(pgn: int) -> str:
|
|
618
|
+
"""Get name for PGN if known.
|
|
619
|
+
|
|
620
|
+
Args:
|
|
621
|
+
pgn: Parameter Group Number.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
PGN name or hex string if unknown.
|
|
625
|
+
"""
|
|
626
|
+
return J1939Decoder.PGN_NAMES.get(pgn, f"PGN_0x{pgn:05X}")
|
|
627
|
+
|
|
628
|
+
@staticmethod
|
|
629
|
+
def extract_signal(data: bytes, byte_pos: int, bit_pos: int, length_bits: int) -> int:
|
|
630
|
+
"""Extract signal value from message data.
|
|
631
|
+
|
|
632
|
+
Args:
|
|
633
|
+
data: Message data bytes.
|
|
634
|
+
byte_pos: Starting byte position (0-indexed).
|
|
635
|
+
bit_pos: Starting bit position within byte (0-7).
|
|
636
|
+
length_bits: Number of bits to extract.
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
Raw signal value as integer.
|
|
640
|
+
"""
|
|
641
|
+
if byte_pos >= len(data):
|
|
642
|
+
return 0
|
|
643
|
+
|
|
644
|
+
value = 0
|
|
645
|
+
bits_read = 0
|
|
646
|
+
|
|
647
|
+
# Read bits across multiple bytes if needed
|
|
648
|
+
current_byte = byte_pos
|
|
649
|
+
current_bit = bit_pos
|
|
650
|
+
|
|
651
|
+
while bits_read < length_bits and current_byte < len(data):
|
|
652
|
+
# How many bits to read from current byte
|
|
653
|
+
bits_available = 8 - current_bit
|
|
654
|
+
bits_to_read = min(length_bits - bits_read, bits_available)
|
|
655
|
+
|
|
656
|
+
# Extract bits from current byte
|
|
657
|
+
mask = ((1 << bits_to_read) - 1) << current_bit
|
|
658
|
+
byte_value = (data[current_byte] & mask) >> current_bit
|
|
659
|
+
|
|
660
|
+
# Add to result
|
|
661
|
+
value |= byte_value << bits_read
|
|
662
|
+
|
|
663
|
+
# Move to next byte
|
|
664
|
+
bits_read += bits_to_read
|
|
665
|
+
current_byte += 1
|
|
666
|
+
current_bit = 0
|
|
667
|
+
|
|
668
|
+
return value
|
|
669
|
+
|
|
670
|
+
@staticmethod
|
|
671
|
+
def decode_signal(message: J1939Message, signal_name: str) -> dict[str, float | str | None]:
|
|
672
|
+
"""Decode a specific signal from a J1939 message.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
message: Decoded J1939 message.
|
|
676
|
+
signal_name: Name of signal to decode.
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
Dictionary with 'value', 'unit', and 'raw' keys.
|
|
680
|
+
Returns None values if signal or PGN not found.
|
|
681
|
+
"""
|
|
682
|
+
# Check if PGN has signal definitions
|
|
683
|
+
if message.pgn not in J1939Decoder.PGN_SIGNALS:
|
|
684
|
+
return {"value": None, "unit": None, "raw": None}
|
|
685
|
+
|
|
686
|
+
signals = J1939Decoder.PGN_SIGNALS[message.pgn]
|
|
687
|
+
|
|
688
|
+
if signal_name not in signals:
|
|
689
|
+
return {"value": None, "unit": None, "raw": None}
|
|
690
|
+
|
|
691
|
+
sig_def = signals[signal_name]
|
|
692
|
+
|
|
693
|
+
# Extract raw value
|
|
694
|
+
byte_val = sig_def["byte"]
|
|
695
|
+
bit_val = sig_def["bit"]
|
|
696
|
+
length_val = sig_def["length"]
|
|
697
|
+
scale_val = sig_def["scale"]
|
|
698
|
+
offset_val = sig_def["offset"]
|
|
699
|
+
|
|
700
|
+
if (
|
|
701
|
+
not isinstance(byte_val, int)
|
|
702
|
+
or not isinstance(bit_val, int)
|
|
703
|
+
or not isinstance(length_val, int)
|
|
704
|
+
):
|
|
705
|
+
return {"value": None, "unit": None, "raw": None}
|
|
706
|
+
if not isinstance(scale_val, (int, float)) or not isinstance(offset_val, (int, float)):
|
|
707
|
+
return {"value": None, "unit": None, "raw": None}
|
|
708
|
+
|
|
709
|
+
raw_value = J1939Decoder.extract_signal(
|
|
710
|
+
message.data,
|
|
711
|
+
byte_val,
|
|
712
|
+
bit_val,
|
|
713
|
+
length_val,
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Apply scaling and offset
|
|
717
|
+
scaled_value = raw_value * scale_val + offset_val
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
"value": scaled_value,
|
|
721
|
+
"unit": sig_def["unit"],
|
|
722
|
+
"raw": raw_value,
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
@staticmethod
|
|
726
|
+
def decode_all_signals(message: J1939Message) -> dict[str, dict[str, float | str | None]]:
|
|
727
|
+
"""Decode all signals from a J1939 message.
|
|
728
|
+
|
|
729
|
+
Args:
|
|
730
|
+
message: Decoded J1939 message.
|
|
731
|
+
|
|
732
|
+
Returns:
|
|
733
|
+
Dictionary mapping signal names to their decoded values.
|
|
734
|
+
Empty dict if PGN has no signal definitions.
|
|
735
|
+
"""
|
|
736
|
+
if message.pgn not in J1939Decoder.PGN_SIGNALS:
|
|
737
|
+
return {}
|
|
738
|
+
|
|
739
|
+
signals = J1939Decoder.PGN_SIGNALS[message.pgn]
|
|
740
|
+
result = {}
|
|
741
|
+
|
|
742
|
+
for signal_name in signals:
|
|
743
|
+
result[signal_name] = J1939Decoder.decode_signal(message, signal_name)
|
|
744
|
+
|
|
745
|
+
return result
|