oscura 0.5.0__py3-none-any.whl → 0.6.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 +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"""DBC (CAN Database) file generator from reverse-engineered CAN protocols.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to generate DBC files from CAN message
|
|
4
|
+
specifications, supporting all DBC elements including messages, signals,
|
|
5
|
+
enums, comments, and attributes.
|
|
6
|
+
|
|
7
|
+
DBC Format Reference:
|
|
8
|
+
Vector DBC File Format Specification
|
|
9
|
+
https://www.csselectronics.com/pages/can-dbc-file-format-explained
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Literal
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DBCGenerator",
|
|
20
|
+
"DBCMessage",
|
|
21
|
+
"DBCNode",
|
|
22
|
+
"DBCSignal",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class DBCSignal:
|
|
28
|
+
"""DBC signal definition.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
name: Signal name (must be valid C identifier).
|
|
32
|
+
start_bit: Starting bit position (0-63).
|
|
33
|
+
bit_length: Signal length in bits (1-64).
|
|
34
|
+
byte_order: Byte order - "little_endian" (Intel) or "big_endian" (Motorola).
|
|
35
|
+
value_type: Value type - "unsigned" or "signed".
|
|
36
|
+
factor: Scaling factor (physical = raw * factor + offset).
|
|
37
|
+
offset: Value offset.
|
|
38
|
+
min_value: Minimum physical value.
|
|
39
|
+
max_value: Maximum physical value.
|
|
40
|
+
unit: Physical unit (e.g., "rpm", "km/h", "°C").
|
|
41
|
+
receivers: List of receiving nodes (ECUs).
|
|
42
|
+
value_table: Optional mapping of raw values to descriptions.
|
|
43
|
+
comment: Signal description/documentation.
|
|
44
|
+
multiplexer_indicator: Multiplexer indicator ("M" for multiplexer, "mX" for
|
|
45
|
+
multiplexed where X is the multiplexer value).
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> signal = DBCSignal(
|
|
49
|
+
... name="EngineSpeed",
|
|
50
|
+
... start_bit=0,
|
|
51
|
+
... bit_length=16,
|
|
52
|
+
... byte_order="little_endian",
|
|
53
|
+
... value_type="unsigned",
|
|
54
|
+
... factor=0.25,
|
|
55
|
+
... offset=0.0,
|
|
56
|
+
... min_value=0.0,
|
|
57
|
+
... max_value=16383.75,
|
|
58
|
+
... unit="rpm",
|
|
59
|
+
... receivers=["Gateway", "Dashboard"],
|
|
60
|
+
... )
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
name: str
|
|
64
|
+
start_bit: int
|
|
65
|
+
bit_length: int
|
|
66
|
+
byte_order: Literal["little_endian", "big_endian"] = "little_endian"
|
|
67
|
+
value_type: Literal["unsigned", "signed"] = "unsigned"
|
|
68
|
+
factor: float = 1.0
|
|
69
|
+
offset: float = 0.0
|
|
70
|
+
min_value: float = 0.0
|
|
71
|
+
max_value: float = 0.0
|
|
72
|
+
unit: str = ""
|
|
73
|
+
receivers: list[str] = field(default_factory=lambda: ["Vector__XXX"])
|
|
74
|
+
value_table: dict[int, str] | None = None
|
|
75
|
+
comment: str = ""
|
|
76
|
+
multiplexer_indicator: str | None = None
|
|
77
|
+
|
|
78
|
+
def __post_init__(self) -> None:
|
|
79
|
+
"""Validate signal definition."""
|
|
80
|
+
if self.bit_length < 1 or self.bit_length > 64:
|
|
81
|
+
raise ValueError(f"bit_length must be 1-64, got {self.bit_length}")
|
|
82
|
+
if self.start_bit < 0:
|
|
83
|
+
raise ValueError(f"start_bit must be >= 0, got {self.start_bit}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class DBCMessage:
|
|
88
|
+
"""DBC message definition.
|
|
89
|
+
|
|
90
|
+
Attributes:
|
|
91
|
+
message_id: CAN message ID (11-bit: 0-0x7FF, 29-bit: 0-0x1FFFFFFF).
|
|
92
|
+
name: Message name (must be valid C identifier).
|
|
93
|
+
dlc: Data Length Code (0-8 for CAN 2.0, up to 64 for CAN-FD).
|
|
94
|
+
sender: Transmitting node (ECU) name.
|
|
95
|
+
signals: List of signals in this message.
|
|
96
|
+
comment: Message description/documentation.
|
|
97
|
+
cycle_time: Message transmission cycle time in milliseconds.
|
|
98
|
+
send_type: Transmission type ("Cyclic", "Event", "IfActive", etc.).
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> msg = DBCMessage(
|
|
102
|
+
... message_id=0x200,
|
|
103
|
+
... name="EngineData",
|
|
104
|
+
... dlc=8,
|
|
105
|
+
... sender="ECU_Engine",
|
|
106
|
+
... signals=[...],
|
|
107
|
+
... comment="Engine status and RPM",
|
|
108
|
+
... cycle_time=10,
|
|
109
|
+
... send_type="Cyclic",
|
|
110
|
+
... )
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
message_id: int
|
|
114
|
+
name: str
|
|
115
|
+
dlc: int
|
|
116
|
+
sender: str = "Vector__XXX"
|
|
117
|
+
signals: list[DBCSignal] = field(default_factory=list)
|
|
118
|
+
comment: str = ""
|
|
119
|
+
cycle_time: int | None = None
|
|
120
|
+
send_type: str = "Cyclic"
|
|
121
|
+
|
|
122
|
+
def __post_init__(self) -> None:
|
|
123
|
+
"""Validate message definition."""
|
|
124
|
+
if self.message_id < 0:
|
|
125
|
+
raise ValueError(f"message_id must be >= 0, got {self.message_id}")
|
|
126
|
+
if self.dlc < 0 or self.dlc > 64:
|
|
127
|
+
raise ValueError(f"dlc must be 0-64, got {self.dlc}")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass
|
|
131
|
+
class DBCNode:
|
|
132
|
+
"""DBC network node (ECU) definition.
|
|
133
|
+
|
|
134
|
+
Attributes:
|
|
135
|
+
name: Node name (must be valid C identifier).
|
|
136
|
+
comment: Node description/documentation.
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> node = DBCNode(name="ECU_Engine", comment="Engine Control Unit")
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
name: str
|
|
143
|
+
comment: str = ""
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class DBCGenerator:
|
|
147
|
+
"""DBC file generator from CAN specifications.
|
|
148
|
+
|
|
149
|
+
This class generates complete DBC files from message and signal definitions,
|
|
150
|
+
including all standard DBC sections (nodes, messages, signals, value tables,
|
|
151
|
+
comments, attributes).
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> gen = DBCGenerator()
|
|
155
|
+
>>> gen.add_node(DBCNode("ECU_Engine", "Engine Control Unit"))
|
|
156
|
+
>>> gen.add_node(DBCNode("Gateway"))
|
|
157
|
+
>>> signal = DBCSignal(
|
|
158
|
+
... name="EngineSpeed",
|
|
159
|
+
... start_bit=0,
|
|
160
|
+
... bit_length=16,
|
|
161
|
+
... factor=0.25,
|
|
162
|
+
... unit="rpm",
|
|
163
|
+
... receivers=["Gateway"],
|
|
164
|
+
... )
|
|
165
|
+
>>> msg = DBCMessage(
|
|
166
|
+
... message_id=0x200,
|
|
167
|
+
... name="EngineData",
|
|
168
|
+
... dlc=8,
|
|
169
|
+
... sender="ECU_Engine",
|
|
170
|
+
... signals=[signal],
|
|
171
|
+
... )
|
|
172
|
+
>>> gen.add_message(msg)
|
|
173
|
+
>>> gen.generate(Path("output.dbc"))
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
VERSION = "1.0"
|
|
177
|
+
|
|
178
|
+
def __init__(self) -> None:
|
|
179
|
+
"""Initialize DBC generator."""
|
|
180
|
+
self.nodes: list[DBCNode] = []
|
|
181
|
+
self.messages: list[DBCMessage] = []
|
|
182
|
+
self.value_tables: dict[str, dict[int, str]] = {}
|
|
183
|
+
self.environment_variables: dict[str, Any] = {}
|
|
184
|
+
|
|
185
|
+
def add_node(self, node: DBCNode) -> None:
|
|
186
|
+
"""Add network node (ECU).
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
node: Node definition to add.
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
>>> gen = DBCGenerator()
|
|
193
|
+
>>> gen.add_node(DBCNode("ECU_Engine", "Engine Control Unit"))
|
|
194
|
+
"""
|
|
195
|
+
self.nodes.append(node)
|
|
196
|
+
|
|
197
|
+
def add_message(self, message: DBCMessage) -> None:
|
|
198
|
+
"""Add CAN message definition.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
message: Message definition to add.
|
|
202
|
+
|
|
203
|
+
Example:
|
|
204
|
+
>>> gen = DBCGenerator()
|
|
205
|
+
>>> msg = DBCMessage(0x200, "EngineData", 8, "ECU_Engine")
|
|
206
|
+
>>> gen.add_message(msg)
|
|
207
|
+
"""
|
|
208
|
+
self.messages.append(message)
|
|
209
|
+
|
|
210
|
+
def add_value_table(self, name: str, values: dict[int, str]) -> None:
|
|
211
|
+
"""Add value table (enum) for signals.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
name: Value table name.
|
|
215
|
+
values: Mapping of raw values to descriptions.
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
>>> gen = DBCGenerator()
|
|
219
|
+
>>> gen.add_value_table("GearPosition", {
|
|
220
|
+
... 0: "Park",
|
|
221
|
+
... 1: "Reverse",
|
|
222
|
+
... 2: "Neutral",
|
|
223
|
+
... 3: "Drive",
|
|
224
|
+
... })
|
|
225
|
+
"""
|
|
226
|
+
self.value_tables[name] = values
|
|
227
|
+
|
|
228
|
+
def generate(self, output_path: Path) -> None:
|
|
229
|
+
"""Generate complete DBC file.
|
|
230
|
+
|
|
231
|
+
DBC file structure (in order):
|
|
232
|
+
1. VERSION
|
|
233
|
+
2. NS_ (New symbols)
|
|
234
|
+
3. BS_ (Bit timing)
|
|
235
|
+
4. BU_ (Network nodes)
|
|
236
|
+
5. VAL_TABLE_ (Value tables)
|
|
237
|
+
6. BO_ (Messages with signals)
|
|
238
|
+
7. CM_ (Comments)
|
|
239
|
+
8. BA_DEF_ (Attribute definitions)
|
|
240
|
+
9. BA_ (Attribute values)
|
|
241
|
+
10. VAL_ (Signal value descriptions)
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
output_path: Path to output DBC file.
|
|
245
|
+
|
|
246
|
+
Example:
|
|
247
|
+
>>> gen = DBCGenerator()
|
|
248
|
+
>>> # ... add nodes, messages, signals ...
|
|
249
|
+
>>> gen.generate(Path("network.dbc"))
|
|
250
|
+
"""
|
|
251
|
+
dbc_lines = []
|
|
252
|
+
|
|
253
|
+
# Header
|
|
254
|
+
dbc_lines.append(self._generate_header())
|
|
255
|
+
|
|
256
|
+
# Nodes
|
|
257
|
+
dbc_lines.append(self._generate_nodes())
|
|
258
|
+
|
|
259
|
+
# Value tables
|
|
260
|
+
value_tables = self._generate_value_tables()
|
|
261
|
+
if value_tables:
|
|
262
|
+
dbc_lines.append(value_tables)
|
|
263
|
+
|
|
264
|
+
# Messages and signals
|
|
265
|
+
dbc_lines.append(self._generate_messages())
|
|
266
|
+
|
|
267
|
+
# Comments
|
|
268
|
+
comments = self._generate_comments()
|
|
269
|
+
if comments:
|
|
270
|
+
dbc_lines.append(comments)
|
|
271
|
+
|
|
272
|
+
# Attributes
|
|
273
|
+
attributes = self._generate_attributes()
|
|
274
|
+
if attributes:
|
|
275
|
+
dbc_lines.append(attributes)
|
|
276
|
+
|
|
277
|
+
# Value descriptions
|
|
278
|
+
value_desc = self._generate_value_descriptions()
|
|
279
|
+
if value_desc:
|
|
280
|
+
dbc_lines.append(value_desc)
|
|
281
|
+
|
|
282
|
+
dbc_content = "\n".join(dbc_lines)
|
|
283
|
+
|
|
284
|
+
# Write to file
|
|
285
|
+
output_path.write_text(dbc_content, encoding="utf-8")
|
|
286
|
+
|
|
287
|
+
def _generate_header(self) -> str:
|
|
288
|
+
"""Generate DBC file header.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Header section with VERSION, NS_, and BS_.
|
|
292
|
+
"""
|
|
293
|
+
return f"""VERSION "{self.VERSION}"
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
NS_ :
|
|
297
|
+
\tNS_DESC_
|
|
298
|
+
\tCM_
|
|
299
|
+
\tBA_DEF_
|
|
300
|
+
\tBA_
|
|
301
|
+
\tVAL_
|
|
302
|
+
\tCAT_DEF_
|
|
303
|
+
\tCAT_
|
|
304
|
+
\tFILTER
|
|
305
|
+
\tBA_DEF_DEF_
|
|
306
|
+
\tEV_DATA_
|
|
307
|
+
\tENVVAR_DATA_
|
|
308
|
+
\tSGTYPE_
|
|
309
|
+
\tSGTYPE_VAL_
|
|
310
|
+
\tBA_DEF_SGTYPE_
|
|
311
|
+
\tBA_SGTYPE_
|
|
312
|
+
\tSIG_TYPE_REF_
|
|
313
|
+
\tVAL_TABLE_
|
|
314
|
+
\tSIG_GROUP_
|
|
315
|
+
\tSIG_VALTYPE_
|
|
316
|
+
\tSIGTYPE_VALTYPE_
|
|
317
|
+
\tBO_TX_BU_
|
|
318
|
+
\tBA_DEF_REL_
|
|
319
|
+
\tBA_REL_
|
|
320
|
+
\tBA_SGTYPE_REL_
|
|
321
|
+
\tSG_MUL_VAL_
|
|
322
|
+
|
|
323
|
+
BS_:"""
|
|
324
|
+
|
|
325
|
+
def _generate_nodes(self) -> str:
|
|
326
|
+
"""Generate BU_ (nodes/ECUs) section.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
BU_ section with all network nodes.
|
|
330
|
+
"""
|
|
331
|
+
if not self.nodes:
|
|
332
|
+
return "BU_:"
|
|
333
|
+
|
|
334
|
+
node_names = " ".join(node.name for node in self.nodes)
|
|
335
|
+
return f"BU_: {node_names}"
|
|
336
|
+
|
|
337
|
+
def _generate_value_tables(self) -> str:
|
|
338
|
+
"""Generate VAL_TABLE_ section.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
VAL_TABLE_ section with all value tables, or empty string if none.
|
|
342
|
+
"""
|
|
343
|
+
if not self.value_tables:
|
|
344
|
+
return ""
|
|
345
|
+
|
|
346
|
+
lines = []
|
|
347
|
+
for name, values in sorted(self.value_tables.items()):
|
|
348
|
+
value_pairs = " ".join(f'{val} "{desc}"' for val, desc in sorted(values.items()))
|
|
349
|
+
lines.append(f"VAL_TABLE_ {name} {value_pairs} ;")
|
|
350
|
+
|
|
351
|
+
return "\n".join(lines)
|
|
352
|
+
|
|
353
|
+
def _generate_messages(self) -> str:
|
|
354
|
+
"""Generate BO_ (messages) section.
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
BO_ section with all messages and their signals.
|
|
358
|
+
"""
|
|
359
|
+
if not self.messages:
|
|
360
|
+
return ""
|
|
361
|
+
|
|
362
|
+
lines = []
|
|
363
|
+
|
|
364
|
+
for message in sorted(self.messages, key=lambda m: m.message_id):
|
|
365
|
+
# Message line: BO_ MessageID MessageName: DLC Sender
|
|
366
|
+
lines.append(f"BO_ {message.message_id} {message.name}: {message.dlc} {message.sender}")
|
|
367
|
+
|
|
368
|
+
# Signal lines
|
|
369
|
+
for signal in message.signals:
|
|
370
|
+
lines.append(self._generate_signal(signal, message.message_id))
|
|
371
|
+
|
|
372
|
+
# Blank line between messages
|
|
373
|
+
lines.append("")
|
|
374
|
+
|
|
375
|
+
return "\n".join(lines)
|
|
376
|
+
|
|
377
|
+
def _generate_signal(self, signal: DBCSignal, message_id: int) -> str:
|
|
378
|
+
"""Generate SG_ (signal) line.
|
|
379
|
+
|
|
380
|
+
Format:
|
|
381
|
+
SG_ SignalName [M|mX] : StartBit|BitLength@ByteOrder ValueType
|
|
382
|
+
(Factor,Offset) [Min|Max] "Unit" Receivers
|
|
383
|
+
|
|
384
|
+
Where:
|
|
385
|
+
ByteOrder: 1 = little endian (Intel), 0 = big endian (Motorola)
|
|
386
|
+
ValueType: + = unsigned, - = signed
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
signal: Signal definition.
|
|
390
|
+
message_id: Parent message ID (used for Motorola bit calculation).
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Formatted signal line.
|
|
394
|
+
"""
|
|
395
|
+
# Determine byte order indicator (1 = Intel/little, 0 = Motorola/big)
|
|
396
|
+
byte_order_indicator = "1" if signal.byte_order == "little_endian" else "0"
|
|
397
|
+
|
|
398
|
+
# Convert start_bit to DBC format
|
|
399
|
+
# For Intel (little-endian): start_bit is LSB position (use as-is)
|
|
400
|
+
# For Motorola (big-endian): start_bit must be MSB position
|
|
401
|
+
if signal.byte_order == "big_endian":
|
|
402
|
+
# Convert from Intel LSB position to Motorola MSB position
|
|
403
|
+
# Motorola start_bit = Intel start_bit + bit_length - 1
|
|
404
|
+
start_bit = signal.start_bit + signal.bit_length - 1
|
|
405
|
+
else:
|
|
406
|
+
start_bit = signal.start_bit
|
|
407
|
+
|
|
408
|
+
# Determine value type (+ = unsigned, - = signed)
|
|
409
|
+
value_type_indicator = "+" if signal.value_type == "unsigned" else "-"
|
|
410
|
+
|
|
411
|
+
# Multiplexer indicator
|
|
412
|
+
multiplex = ""
|
|
413
|
+
if signal.multiplexer_indicator:
|
|
414
|
+
multiplex = f" {signal.multiplexer_indicator}"
|
|
415
|
+
|
|
416
|
+
# Receivers
|
|
417
|
+
receivers = ",".join(signal.receivers)
|
|
418
|
+
|
|
419
|
+
return (
|
|
420
|
+
f" SG_ {signal.name}{multiplex} : {start_bit}|{signal.bit_length}@"
|
|
421
|
+
f"{byte_order_indicator}{value_type_indicator} "
|
|
422
|
+
f"({signal.factor},{signal.offset}) "
|
|
423
|
+
f"[{signal.min_value}|{signal.max_value}] "
|
|
424
|
+
f'"{signal.unit}" {receivers}'
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def _generate_comments(self) -> str:
|
|
428
|
+
"""Generate CM_ (comments) section.
|
|
429
|
+
|
|
430
|
+
Returns:
|
|
431
|
+
CM_ section with all comments, or empty string if none.
|
|
432
|
+
"""
|
|
433
|
+
lines = []
|
|
434
|
+
|
|
435
|
+
# Node comments
|
|
436
|
+
for node in self.nodes:
|
|
437
|
+
if node.comment:
|
|
438
|
+
lines.append(f'CM_ BU_ {node.name} "{node.comment}";')
|
|
439
|
+
|
|
440
|
+
# Message comments
|
|
441
|
+
for message in self.messages:
|
|
442
|
+
if message.comment:
|
|
443
|
+
lines.append(f'CM_ BO_ {message.message_id} "{message.comment}";')
|
|
444
|
+
|
|
445
|
+
# Signal comments
|
|
446
|
+
for message in self.messages:
|
|
447
|
+
for signal in message.signals:
|
|
448
|
+
if signal.comment:
|
|
449
|
+
lines.append(f'CM_ SG_ {message.message_id} {signal.name} "{signal.comment}";')
|
|
450
|
+
|
|
451
|
+
return "\n".join(lines) if lines else ""
|
|
452
|
+
|
|
453
|
+
def _generate_attributes(self) -> str:
|
|
454
|
+
"""Generate BA_ (attributes) section.
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
BA_ section with attribute definitions and values.
|
|
458
|
+
"""
|
|
459
|
+
lines = []
|
|
460
|
+
|
|
461
|
+
# Define standard attributes
|
|
462
|
+
lines.append('BA_DEF_ "BusType" STRING ;')
|
|
463
|
+
lines.append('BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;')
|
|
464
|
+
lines.append('BA_DEF_ BO_ "GenMsgSendType" STRING ;')
|
|
465
|
+
|
|
466
|
+
# Default attribute values
|
|
467
|
+
lines.append('BA_DEF_DEF_ "BusType" "CAN";')
|
|
468
|
+
lines.append('BA_DEF_DEF_ "GenMsgCycleTime" 0;')
|
|
469
|
+
lines.append('BA_DEF_DEF_ "GenMsgSendType" "Cyclic";')
|
|
470
|
+
|
|
471
|
+
# Message-specific attribute values
|
|
472
|
+
for message in self.messages:
|
|
473
|
+
if message.cycle_time is not None:
|
|
474
|
+
lines.append(
|
|
475
|
+
f'BA_ "GenMsgCycleTime" BO_ {message.message_id} {message.cycle_time};'
|
|
476
|
+
)
|
|
477
|
+
if message.send_type:
|
|
478
|
+
lines.append(
|
|
479
|
+
f'BA_ "GenMsgSendType" BO_ {message.message_id} "{message.send_type}";'
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
return "\n".join(lines)
|
|
483
|
+
|
|
484
|
+
def _generate_value_descriptions(self) -> str:
|
|
485
|
+
"""Generate VAL_ (signal value descriptions) section.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
VAL_ section with signal value descriptions, or empty string if none.
|
|
489
|
+
"""
|
|
490
|
+
lines = []
|
|
491
|
+
|
|
492
|
+
for message in self.messages:
|
|
493
|
+
for signal in message.signals:
|
|
494
|
+
if signal.value_table:
|
|
495
|
+
value_pairs = " ".join(
|
|
496
|
+
f'{val} "{desc}"' for val, desc in sorted(signal.value_table.items())
|
|
497
|
+
)
|
|
498
|
+
lines.append(f"VAL_ {message.message_id} {signal.name} {value_pairs} ;")
|
|
499
|
+
|
|
500
|
+
return "\n".join(lines) if lines else ""
|
|
501
|
+
|
|
502
|
+
def _calculate_motorola_start_bit(self, start_bit: int, bit_length: int) -> int:
|
|
503
|
+
"""Calculate start bit for Motorola (big-endian) byte order.
|
|
504
|
+
|
|
505
|
+
In DBC files, Motorola (big-endian) signals use the MSB position as the start bit.
|
|
506
|
+
Intel (little-endian) signals use the LSB position as the start bit.
|
|
507
|
+
|
|
508
|
+
For Motorola byte order, the start bit is simply the MSB position,
|
|
509
|
+
which is start_bit + bit_length - 1.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
start_bit: Intel (little-endian) start bit position (LSB).
|
|
513
|
+
bit_length: Signal length in bits.
|
|
514
|
+
|
|
515
|
+
Returns:
|
|
516
|
+
Motorola (big-endian) start bit position (MSB).
|
|
517
|
+
|
|
518
|
+
Example:
|
|
519
|
+
>>> gen = DBCGenerator()
|
|
520
|
+
>>> # 8-bit signal starting at bit 0 (Intel LSB)
|
|
521
|
+
>>> # Bits 0-7 -> MSB is at bit 7
|
|
522
|
+
>>> gen._calculate_motorola_start_bit(0, 8)
|
|
523
|
+
7
|
|
524
|
+
>>> # 16-bit signal starting at bit 0 (Intel LSB)
|
|
525
|
+
>>> # Bits 0-15 -> MSB is at bit 15
|
|
526
|
+
>>> gen._calculate_motorola_start_bit(0, 16)
|
|
527
|
+
15
|
|
528
|
+
"""
|
|
529
|
+
# Motorola start bit is simply the MSB position
|
|
530
|
+
return start_bit + bit_length - 1
|
|
531
|
+
|
|
532
|
+
def validate_dbc(self, dbc_content: str) -> bool:
|
|
533
|
+
"""Validate generated DBC syntax.
|
|
534
|
+
|
|
535
|
+
Basic syntax validation checking for:
|
|
536
|
+
- Required sections (VERSION, NS_, BS_, BU_)
|
|
537
|
+
- Well-formed message definitions
|
|
538
|
+
- Well-formed signal definitions
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
dbc_content: DBC file content to validate.
|
|
542
|
+
|
|
543
|
+
Returns:
|
|
544
|
+
True if basic syntax is valid, False otherwise.
|
|
545
|
+
|
|
546
|
+
Example:
|
|
547
|
+
>>> gen = DBCGenerator()
|
|
548
|
+
>>> gen.add_node(DBCNode("Test"))
|
|
549
|
+
>>> msg = DBCMessage(0x100, "TestMsg", 8, "Test")
|
|
550
|
+
>>> gen.add_message(msg)
|
|
551
|
+
>>> from pathlib import Path
|
|
552
|
+
>>> import tempfile
|
|
553
|
+
>>> with tempfile.NamedTemporaryFile(mode='w', suffix='.dbc', delete=False) as f:
|
|
554
|
+
... gen.generate(Path(f.name))
|
|
555
|
+
... content = Path(f.name).read_text()
|
|
556
|
+
... gen.validate_dbc(content)
|
|
557
|
+
True
|
|
558
|
+
"""
|
|
559
|
+
lines = dbc_content.strip().split("\n")
|
|
560
|
+
|
|
561
|
+
# Check for required sections
|
|
562
|
+
has_version = any(line.startswith("VERSION") for line in lines)
|
|
563
|
+
has_ns = any(line.startswith("NS_") for line in lines)
|
|
564
|
+
has_bs = any(line.startswith("BS_") for line in lines)
|
|
565
|
+
has_bu = any(line.startswith("BU_") for line in lines)
|
|
566
|
+
|
|
567
|
+
if not (has_version and has_ns and has_bs and has_bu):
|
|
568
|
+
return False
|
|
569
|
+
|
|
570
|
+
# Check message definitions (BO_)
|
|
571
|
+
for line in lines:
|
|
572
|
+
if line.startswith("BO_ "):
|
|
573
|
+
# Format: BO_ <ID> <Name>: <DLC> <Sender>
|
|
574
|
+
parts = line.split()
|
|
575
|
+
if len(parts) < 5:
|
|
576
|
+
return False
|
|
577
|
+
try:
|
|
578
|
+
int(parts[1]) # Message ID
|
|
579
|
+
int(parts[3]) # DLC (after colon)
|
|
580
|
+
except (ValueError, IndexError):
|
|
581
|
+
return False
|
|
582
|
+
|
|
583
|
+
# Check signal definitions (SG_)
|
|
584
|
+
for line in lines:
|
|
585
|
+
if line.strip().startswith("SG_ "):
|
|
586
|
+
# Format: SG_ <Name> : <StartBit>|<Length>@<ByteOrder><ValueType> ...
|
|
587
|
+
if "|" not in line or "@" not in line:
|
|
588
|
+
return False
|
|
589
|
+
|
|
590
|
+
return True
|