oscura 0.5.1__py3-none-any.whl → 0.7.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/edges.py +325 -65
- 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/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 +17 -102
- 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/{schemas → core/schemas}/device_mapping.json +2 -8
- oscura/{schemas → core/schemas}/packet_format.json +4 -24
- oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
- 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 -8
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- 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/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 +183 -67
- 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/tss.py +456 -0
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -0
- 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/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/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 +1 -1
- 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.7.0.dist-info/METADATA +661 -0
- oscura-0.7.0.dist-info/RECORD +591 -0
- 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/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 -291
- 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/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/workflow/__init__.py +0 -13
- oscura-0.5.1.dist-info/METADATA +0 -583
- oscura-0.5.1.dist-info/RECORD +0 -481
- /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/{schemas → core/schemas}/bus_configuration.json +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.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""FlexRay protocol analyzer.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive FlexRay frame analysis including header parsing,
|
|
4
|
+
CRC validation, signal decoding, and multi-channel support.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
>>> from oscura.automotive.flexray import FlexRayAnalyzer, FlexRaySignal
|
|
8
|
+
>>> analyzer = FlexRayAnalyzer()
|
|
9
|
+
>>> frame = analyzer.parse_frame(data, timestamp=0.0, channel="A")
|
|
10
|
+
>>> print(f"Slot {frame.header.frame_id}, valid: {frame.crc_valid}")
|
|
11
|
+
>>>
|
|
12
|
+
>>> # Add signal definitions
|
|
13
|
+
>>> signal = FlexRaySignal(
|
|
14
|
+
... name="EngineSpeed",
|
|
15
|
+
... frame_id=100,
|
|
16
|
+
... start_bit=0,
|
|
17
|
+
... bit_length=16,
|
|
18
|
+
... factor=0.25,
|
|
19
|
+
... offset=0,
|
|
20
|
+
... unit="rpm"
|
|
21
|
+
... )
|
|
22
|
+
>>> analyzer.add_signal(signal)
|
|
23
|
+
>>> signals = analyzer.decode_signals(100, frame.payload)
|
|
24
|
+
>>> print(f"Engine Speed: {signals['EngineSpeed']} rpm")
|
|
25
|
+
|
|
26
|
+
References:
|
|
27
|
+
FlexRay Communications System Protocol Specification Version 3.0.1
|
|
28
|
+
ISO 17458 (FlexRay standard)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from oscura.automotive.flexray.crc import (
|
|
37
|
+
verify_frame_crc,
|
|
38
|
+
verify_header_crc,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class FlexRayHeader:
|
|
44
|
+
"""FlexRay frame header (40 bits / 5 bytes).
|
|
45
|
+
|
|
46
|
+
The header contains frame identification, control flags, and CRC.
|
|
47
|
+
|
|
48
|
+
Attributes:
|
|
49
|
+
reserved: Reserved bit (always 0).
|
|
50
|
+
payload_preamble: Payload preamble indicator.
|
|
51
|
+
null_frame: Null frame indicator (no payload).
|
|
52
|
+
sync_frame: Synchronization frame indicator.
|
|
53
|
+
startup_frame: Startup frame indicator.
|
|
54
|
+
frame_id: Frame identifier (1-2047).
|
|
55
|
+
payload_length: Payload length in bytes (0-254).
|
|
56
|
+
header_crc: 11-bit header CRC.
|
|
57
|
+
cycle_count: Cycle counter (0-63).
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
>>> header = FlexRayHeader(
|
|
61
|
+
... reserved=0, payload_preamble=0, null_frame=False,
|
|
62
|
+
... sync_frame=True, startup_frame=False, frame_id=100,
|
|
63
|
+
... payload_length=10, header_crc=0x3A5, cycle_count=5
|
|
64
|
+
... )
|
|
65
|
+
>>> print(f"Frame ID: {header.frame_id}, Cycle: {header.cycle_count}")
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
reserved: int # 1 bit
|
|
69
|
+
payload_preamble: int # 1 bit
|
|
70
|
+
null_frame: bool # 1 bit
|
|
71
|
+
sync_frame: bool # 1 bit
|
|
72
|
+
startup_frame: bool # 1 bit
|
|
73
|
+
frame_id: int # 11 bits (1-2047)
|
|
74
|
+
payload_length: int # In bytes (0-254, stored as words internally)
|
|
75
|
+
header_crc: int # 11 bits
|
|
76
|
+
cycle_count: int # 6 bits (0-63)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class FlexRayFrame:
|
|
81
|
+
"""FlexRay frame representation.
|
|
82
|
+
|
|
83
|
+
Attributes:
|
|
84
|
+
timestamp: Frame timestamp in seconds.
|
|
85
|
+
channel: Channel identifier ("A" or "B").
|
|
86
|
+
header: Parsed frame header.
|
|
87
|
+
payload: Frame payload bytes (0-254 bytes).
|
|
88
|
+
frame_crc: Received 24-bit frame CRC.
|
|
89
|
+
crc_valid: True if frame CRC is valid.
|
|
90
|
+
segment_type: Segment type ("static" or "dynamic").
|
|
91
|
+
decoded_signals: Decoded signal values (populated after decoding).
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> frame = FlexRayFrame(
|
|
95
|
+
... timestamp=1.234, channel="A", header=header,
|
|
96
|
+
... payload=b"\\x01\\x02\\x03", frame_crc=0x123456,
|
|
97
|
+
... crc_valid=True, segment_type="static"
|
|
98
|
+
... )
|
|
99
|
+
>>> print(f"Channel {frame.channel}, Slot {frame.header.frame_id}")
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
timestamp: float
|
|
103
|
+
channel: str # "A" or "B"
|
|
104
|
+
header: FlexRayHeader
|
|
105
|
+
payload: bytes # 0-254 bytes
|
|
106
|
+
frame_crc: int # 24 bits
|
|
107
|
+
crc_valid: bool
|
|
108
|
+
segment_type: str = "static" # "static" or "dynamic"
|
|
109
|
+
decoded_signals: dict[str, Any] = field(default_factory=dict)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class FlexRaySignal:
|
|
114
|
+
"""FlexRay signal definition.
|
|
115
|
+
|
|
116
|
+
Defines how to extract and decode a signal from a frame payload.
|
|
117
|
+
|
|
118
|
+
Attributes:
|
|
119
|
+
name: Signal name.
|
|
120
|
+
frame_id: Frame ID containing this signal.
|
|
121
|
+
start_bit: Start bit position in payload.
|
|
122
|
+
bit_length: Signal length in bits.
|
|
123
|
+
byte_order: Byte order ("big_endian" or "little_endian").
|
|
124
|
+
factor: Scaling factor (physical = raw * factor + offset).
|
|
125
|
+
offset: Offset for physical value.
|
|
126
|
+
unit: Physical unit (e.g., "rpm", "km/h").
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
>>> signal = FlexRaySignal(
|
|
130
|
+
... name="VehicleSpeed",
|
|
131
|
+
... frame_id=200,
|
|
132
|
+
... start_bit=16,
|
|
133
|
+
... bit_length=16,
|
|
134
|
+
... byte_order="big_endian",
|
|
135
|
+
... factor=0.01,
|
|
136
|
+
... offset=0,
|
|
137
|
+
... unit="km/h"
|
|
138
|
+
... )
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
name: str
|
|
142
|
+
frame_id: int
|
|
143
|
+
start_bit: int
|
|
144
|
+
bit_length: int
|
|
145
|
+
byte_order: str = "big_endian" # FlexRay is typically big-endian
|
|
146
|
+
factor: float = 1.0
|
|
147
|
+
offset: float = 0.0
|
|
148
|
+
unit: str = ""
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FlexRayAnalyzer:
|
|
152
|
+
"""FlexRay protocol analyzer.
|
|
153
|
+
|
|
154
|
+
Provides comprehensive FlexRay frame analysis including parsing, CRC validation,
|
|
155
|
+
signal decoding, and FIBEX support.
|
|
156
|
+
|
|
157
|
+
Attributes:
|
|
158
|
+
MAX_FRAME_ID: Maximum frame ID (2047).
|
|
159
|
+
MAX_PAYLOAD_LENGTH: Maximum payload length in bytes (254).
|
|
160
|
+
HEADER_LENGTH: Header length in bytes (5).
|
|
161
|
+
CRC_LENGTH: CRC length in bytes (3).
|
|
162
|
+
STATIC_SEGMENT: Static segment identifier.
|
|
163
|
+
DYNAMIC_SEGMENT: Dynamic segment identifier.
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> analyzer = FlexRayAnalyzer()
|
|
167
|
+
>>> frame = analyzer.parse_frame(raw_data, timestamp=1.0, channel="A")
|
|
168
|
+
>>> if frame.crc_valid:
|
|
169
|
+
... print(f"Valid frame on slot {frame.header.frame_id}")
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
# FlexRay constants
|
|
173
|
+
MAX_FRAME_ID = 2047
|
|
174
|
+
MAX_PAYLOAD_LENGTH = 254 # bytes (127 16-bit words)
|
|
175
|
+
HEADER_LENGTH = 5 # bytes (40 bits)
|
|
176
|
+
CRC_LENGTH = 3 # bytes (24 bits)
|
|
177
|
+
|
|
178
|
+
# Segment types
|
|
179
|
+
STATIC_SEGMENT = "static"
|
|
180
|
+
DYNAMIC_SEGMENT = "dynamic"
|
|
181
|
+
|
|
182
|
+
def __init__(self, cluster_config: dict[str, Any] | None = None) -> None:
|
|
183
|
+
"""Initialize FlexRay analyzer.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
cluster_config: Optional cluster configuration containing:
|
|
187
|
+
- static_slot_count: Number of static slots
|
|
188
|
+
- dynamic_slot_count: Number of dynamic slots
|
|
189
|
+
- sample_rate: Sample rate in Hz
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
>>> config = {"static_slot_count": 100, "dynamic_slot_count": 50}
|
|
193
|
+
>>> analyzer = FlexRayAnalyzer(cluster_config=config)
|
|
194
|
+
"""
|
|
195
|
+
self.frames: list[FlexRayFrame] = []
|
|
196
|
+
self.signals: list[FlexRaySignal] = []
|
|
197
|
+
self.cluster_config = cluster_config or {}
|
|
198
|
+
|
|
199
|
+
def parse_frame(self, data: bytes, timestamp: float = 0.0, channel: str = "A") -> FlexRayFrame:
|
|
200
|
+
"""Parse FlexRay frame from raw bytes.
|
|
201
|
+
|
|
202
|
+
The frame structure is:
|
|
203
|
+
- Header (5 bytes)
|
|
204
|
+
- Payload (0-254 bytes)
|
|
205
|
+
- CRC (3 bytes)
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
data: Raw frame data (minimum 8 bytes: 5 header + 0 payload + 3 CRC).
|
|
209
|
+
timestamp: Frame timestamp in seconds.
|
|
210
|
+
channel: Channel identifier ("A" or "B").
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Parsed FlexRay frame.
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
ValueError: If data is too short or invalid.
|
|
217
|
+
|
|
218
|
+
Example:
|
|
219
|
+
>>> raw_data = bytes([0x00, 0x64, 0x12, 0x34, 0x05] + [0] * 10 + [0, 0, 0])
|
|
220
|
+
>>> frame = analyzer.parse_frame(raw_data, timestamp=1.0, channel="A")
|
|
221
|
+
>>> print(f"Frame ID: {frame.header.frame_id}")
|
|
222
|
+
"""
|
|
223
|
+
if len(data) < self.HEADER_LENGTH + self.CRC_LENGTH:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"Frame data too short: {len(data)} bytes "
|
|
226
|
+
f"(minimum {self.HEADER_LENGTH + self.CRC_LENGTH})"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Extract header, payload, and CRC
|
|
230
|
+
header_bytes = data[: self.HEADER_LENGTH]
|
|
231
|
+
crc_bytes = data[-self.CRC_LENGTH :]
|
|
232
|
+
payload_bytes = data[self.HEADER_LENGTH : -self.CRC_LENGTH]
|
|
233
|
+
|
|
234
|
+
# Parse header
|
|
235
|
+
header = self._parse_header(header_bytes)
|
|
236
|
+
|
|
237
|
+
# Verify payload length matches header
|
|
238
|
+
expected_payload_length = header.payload_length
|
|
239
|
+
if len(payload_bytes) != expected_payload_length:
|
|
240
|
+
raise ValueError(
|
|
241
|
+
f"Payload length mismatch: expected {expected_payload_length}, "
|
|
242
|
+
f"got {len(payload_bytes)}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Parse CRC
|
|
246
|
+
frame_crc = int.from_bytes(crc_bytes, "big")
|
|
247
|
+
|
|
248
|
+
# Verify CRC
|
|
249
|
+
crc_valid = verify_frame_crc(header_bytes, payload_bytes, frame_crc)
|
|
250
|
+
|
|
251
|
+
# Determine segment type
|
|
252
|
+
segment_type = self._determine_segment_type(header.frame_id)
|
|
253
|
+
|
|
254
|
+
# Create frame
|
|
255
|
+
frame = FlexRayFrame(
|
|
256
|
+
timestamp=timestamp,
|
|
257
|
+
channel=channel,
|
|
258
|
+
header=header,
|
|
259
|
+
payload=payload_bytes,
|
|
260
|
+
frame_crc=frame_crc,
|
|
261
|
+
crc_valid=crc_valid,
|
|
262
|
+
segment_type=segment_type,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
self.frames.append(frame)
|
|
266
|
+
return frame
|
|
267
|
+
|
|
268
|
+
def _parse_header(self, header_bytes: bytes) -> FlexRayHeader:
|
|
269
|
+
"""Parse 40-bit FlexRay header.
|
|
270
|
+
|
|
271
|
+
Header Format (5 bytes, 40 bits, MSB first):
|
|
272
|
+
Byte 0, bit 7 (MSB): Reserved (1 bit)
|
|
273
|
+
Byte 0, bit 6: Payload Preamble Indicator (1 bit)
|
|
274
|
+
Byte 0, bit 5: Null Frame Indicator (1 bit)
|
|
275
|
+
Byte 0, bit 4: Sync Frame Indicator (1 bit)
|
|
276
|
+
Byte 0, bit 3: Startup Frame Indicator (1 bit)
|
|
277
|
+
Byte 0, bits 2-0 + Byte 1, bits 7-0: Frame ID (11 bits, 1-2047)
|
|
278
|
+
Byte 2, bits 7-1: Payload Length (7 bits, in 16-bit words)
|
|
279
|
+
Byte 2, bit 0 + Byte 3, bits 7-6: Header CRC (11 bits)
|
|
280
|
+
Byte 3, bits 5-0: Cycle Count (6 bits, 0-63)
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
header_bytes: 5-byte header data.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Parsed FlexRay header.
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
ValueError: If header is invalid.
|
|
290
|
+
"""
|
|
291
|
+
if len(header_bytes) < 5:
|
|
292
|
+
raise ValueError(f"FlexRay header must be 5 bytes, got {len(header_bytes)}")
|
|
293
|
+
|
|
294
|
+
# Convert to 40-bit integer (big-endian)
|
|
295
|
+
header_int = int.from_bytes(header_bytes[:5], "big")
|
|
296
|
+
|
|
297
|
+
# Extract fields (MSB first, bit 39 is leftmost)
|
|
298
|
+
reserved = (header_int >> 39) & 0x01
|
|
299
|
+
payload_preamble = (header_int >> 38) & 0x01
|
|
300
|
+
null_frame = bool((header_int >> 37) & 0x01)
|
|
301
|
+
sync_frame = bool((header_int >> 36) & 0x01)
|
|
302
|
+
startup_frame = bool((header_int >> 35) & 0x01)
|
|
303
|
+
frame_id = (header_int >> 24) & 0x7FF # 11 bits
|
|
304
|
+
payload_length_words = (header_int >> 17) & 0x7F # 7 bits (in words)
|
|
305
|
+
header_crc = (header_int >> 6) & 0x7FF # 11 bits
|
|
306
|
+
cycle_count = header_int & 0x3F # 6 bits
|
|
307
|
+
|
|
308
|
+
# Convert payload length from words to bytes
|
|
309
|
+
payload_length = payload_length_words * 2
|
|
310
|
+
|
|
311
|
+
# Validate frame ID
|
|
312
|
+
if frame_id < 1 or frame_id > self.MAX_FRAME_ID:
|
|
313
|
+
raise ValueError(f"Invalid frame ID: {frame_id} (must be 1-{self.MAX_FRAME_ID})")
|
|
314
|
+
|
|
315
|
+
# Verify header CRC
|
|
316
|
+
header_crc_valid = verify_header_crc(
|
|
317
|
+
reserved,
|
|
318
|
+
payload_preamble,
|
|
319
|
+
int(null_frame),
|
|
320
|
+
int(sync_frame),
|
|
321
|
+
int(startup_frame),
|
|
322
|
+
frame_id,
|
|
323
|
+
payload_length_words,
|
|
324
|
+
header_crc,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if not header_crc_valid:
|
|
328
|
+
# Note: We don't raise an error, but we could log a warning
|
|
329
|
+
pass
|
|
330
|
+
|
|
331
|
+
return FlexRayHeader(
|
|
332
|
+
reserved=reserved,
|
|
333
|
+
payload_preamble=payload_preamble,
|
|
334
|
+
null_frame=null_frame,
|
|
335
|
+
sync_frame=sync_frame,
|
|
336
|
+
startup_frame=startup_frame,
|
|
337
|
+
frame_id=frame_id,
|
|
338
|
+
payload_length=payload_length,
|
|
339
|
+
header_crc=header_crc,
|
|
340
|
+
cycle_count=cycle_count,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def _determine_segment_type(self, frame_id: int) -> str:
|
|
344
|
+
"""Determine if frame is in static or dynamic segment.
|
|
345
|
+
|
|
346
|
+
Static segment frames have IDs from 1 to static_slot_count.
|
|
347
|
+
Dynamic segment frames have IDs from static_slot_count+1 to 2047.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
frame_id: Frame ID to classify.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Segment type ("static" or "dynamic").
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
>>> analyzer.cluster_config = {"static_slot_count": 100}
|
|
357
|
+
>>> segment = analyzer._determine_segment_type(50)
|
|
358
|
+
>>> print(segment) # "static"
|
|
359
|
+
>>> segment = analyzer._determine_segment_type(150)
|
|
360
|
+
>>> print(segment) # "dynamic"
|
|
361
|
+
"""
|
|
362
|
+
static_slot_count = self.cluster_config.get("static_slot_count", 100)
|
|
363
|
+
|
|
364
|
+
if frame_id <= static_slot_count:
|
|
365
|
+
return self.STATIC_SEGMENT
|
|
366
|
+
else:
|
|
367
|
+
return self.DYNAMIC_SEGMENT
|
|
368
|
+
|
|
369
|
+
def add_signal(self, signal: FlexRaySignal) -> None:
|
|
370
|
+
"""Add signal definition for decoding.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
signal: Signal definition to add.
|
|
374
|
+
|
|
375
|
+
Example:
|
|
376
|
+
>>> signal = FlexRaySignal(
|
|
377
|
+
... name="EngineSpeed", frame_id=100, start_bit=0,
|
|
378
|
+
... bit_length=16, factor=0.25, unit="rpm"
|
|
379
|
+
... )
|
|
380
|
+
>>> analyzer.add_signal(signal)
|
|
381
|
+
"""
|
|
382
|
+
self.signals.append(signal)
|
|
383
|
+
|
|
384
|
+
def decode_signals(self, frame_id: int, payload: bytes) -> dict[str, Any]:
|
|
385
|
+
"""Decode signals from frame payload.
|
|
386
|
+
|
|
387
|
+
Extracts and scales signal values according to signal definitions.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
frame_id: Frame ID to decode.
|
|
391
|
+
payload: Frame payload bytes.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
Dictionary mapping signal names to physical values.
|
|
395
|
+
|
|
396
|
+
Example:
|
|
397
|
+
>>> signals = analyzer.decode_signals(100, b"\\x00\\x64\\x01\\x00")
|
|
398
|
+
>>> print(f"EngineSpeed: {signals['EngineSpeed']} rpm")
|
|
399
|
+
"""
|
|
400
|
+
decoded: dict[str, Any] = {}
|
|
401
|
+
|
|
402
|
+
# Find signals for this frame
|
|
403
|
+
frame_signals = [s for s in self.signals if s.frame_id == frame_id]
|
|
404
|
+
|
|
405
|
+
for signal in frame_signals:
|
|
406
|
+
# Extract raw value
|
|
407
|
+
raw_value = self._extract_signal_value(payload, signal)
|
|
408
|
+
|
|
409
|
+
# Apply scaling
|
|
410
|
+
physical_value = raw_value * signal.factor + signal.offset
|
|
411
|
+
|
|
412
|
+
decoded[signal.name] = physical_value
|
|
413
|
+
|
|
414
|
+
return decoded
|
|
415
|
+
|
|
416
|
+
def _extract_signal_value(self, payload: bytes, signal: FlexRaySignal) -> int:
|
|
417
|
+
"""Extract raw signal value from payload.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
payload: Frame payload bytes.
|
|
421
|
+
signal: Signal definition.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Raw signal value (unscaled).
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
ValueError: If signal extends beyond payload.
|
|
428
|
+
"""
|
|
429
|
+
# Calculate byte range
|
|
430
|
+
start_byte = signal.start_bit // 8
|
|
431
|
+
end_byte = (signal.start_bit + signal.bit_length - 1) // 8 + 1
|
|
432
|
+
|
|
433
|
+
if end_byte > len(payload):
|
|
434
|
+
raise ValueError(
|
|
435
|
+
f"Signal {signal.name} extends beyond payload: "
|
|
436
|
+
f"needs bytes {start_byte}-{end_byte}, payload has {len(payload)}"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# Extract bytes
|
|
440
|
+
signal_bytes = payload[start_byte:end_byte]
|
|
441
|
+
|
|
442
|
+
# Convert to integer based on byte order
|
|
443
|
+
if signal.byte_order == "big_endian":
|
|
444
|
+
value = int.from_bytes(signal_bytes, "big")
|
|
445
|
+
else:
|
|
446
|
+
value = int.from_bytes(signal_bytes, "little")
|
|
447
|
+
|
|
448
|
+
# Extract specific bits
|
|
449
|
+
bit_offset = signal.start_bit % 8
|
|
450
|
+
mask = (1 << signal.bit_length) - 1
|
|
451
|
+
value = (value >> bit_offset) & mask
|
|
452
|
+
|
|
453
|
+
return value
|
|
454
|
+
|
|
455
|
+
def get_frame_statistics(self) -> dict[str, Any]:
|
|
456
|
+
"""Get statistics about parsed frames.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Dictionary with frame statistics including:
|
|
460
|
+
- total_frames: Total number of frames
|
|
461
|
+
- frames_by_channel: Count per channel
|
|
462
|
+
- frames_by_id: Count per frame ID
|
|
463
|
+
- crc_errors: Number of CRC errors
|
|
464
|
+
- segment_distribution: Static vs dynamic frame counts
|
|
465
|
+
|
|
466
|
+
Example:
|
|
467
|
+
>>> stats = analyzer.get_frame_statistics()
|
|
468
|
+
>>> print(f"Total frames: {stats['total_frames']}")
|
|
469
|
+
>>> print(f"CRC errors: {stats['crc_errors']}")
|
|
470
|
+
"""
|
|
471
|
+
stats: dict[str, Any] = {
|
|
472
|
+
"total_frames": len(self.frames),
|
|
473
|
+
"frames_by_channel": {},
|
|
474
|
+
"frames_by_id": {},
|
|
475
|
+
"crc_errors": 0,
|
|
476
|
+
"segment_distribution": {"static": 0, "dynamic": 0},
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
for frame in self.frames:
|
|
480
|
+
# Count by channel
|
|
481
|
+
stats["frames_by_channel"][frame.channel] = (
|
|
482
|
+
stats["frames_by_channel"].get(frame.channel, 0) + 1
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Count by frame ID
|
|
486
|
+
frame_id = frame.header.frame_id
|
|
487
|
+
stats["frames_by_id"][frame_id] = stats["frames_by_id"].get(frame_id, 0) + 1
|
|
488
|
+
|
|
489
|
+
# Count CRC errors
|
|
490
|
+
if not frame.crc_valid:
|
|
491
|
+
stats["crc_errors"] += 1
|
|
492
|
+
|
|
493
|
+
# Count segment types
|
|
494
|
+
stats["segment_distribution"][frame.segment_type] += 1
|
|
495
|
+
|
|
496
|
+
return stats
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
__all__ = [
|
|
500
|
+
"FlexRayAnalyzer",
|
|
501
|
+
"FlexRayFrame",
|
|
502
|
+
"FlexRayHeader",
|
|
503
|
+
"FlexRaySignal",
|
|
504
|
+
]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""FlexRay CRC calculation algorithms.
|
|
2
|
+
|
|
3
|
+
This module implements FlexRay CRC algorithms:
|
|
4
|
+
- Header CRC-11 (protects frame header)
|
|
5
|
+
- Frame CRC-24 (protects header + payload)
|
|
6
|
+
|
|
7
|
+
References:
|
|
8
|
+
FlexRay Communications System Protocol Specification Version 3.0.1
|
|
9
|
+
Section 4.5: Error Detection Mechanisms
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def calculate_header_crc(
|
|
16
|
+
reserved: int,
|
|
17
|
+
payload_preamble: int,
|
|
18
|
+
null_frame: int,
|
|
19
|
+
sync_frame: int,
|
|
20
|
+
startup_frame: int,
|
|
21
|
+
frame_id: int,
|
|
22
|
+
payload_length: int,
|
|
23
|
+
) -> int:
|
|
24
|
+
"""Calculate 11-bit FlexRay header CRC.
|
|
25
|
+
|
|
26
|
+
The header CRC protects the first 29 bits of the header (excluding the CRC
|
|
27
|
+
itself and the cycle count field).
|
|
28
|
+
|
|
29
|
+
CRC polynomial: x^11 + x^9 + x^8 + x^7 + x^2 + 1 (0x385)
|
|
30
|
+
Initial value: 0x01A
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
reserved: Reserved bit (1 bit).
|
|
34
|
+
payload_preamble: Payload preamble indicator (1 bit).
|
|
35
|
+
null_frame: Null frame indicator (1 bit).
|
|
36
|
+
sync_frame: Sync frame indicator (1 bit).
|
|
37
|
+
startup_frame: Startup frame indicator (1 bit).
|
|
38
|
+
frame_id: Frame ID (11 bits, 1-2047).
|
|
39
|
+
payload_length: Payload length in 16-bit words (7 bits, 0-127).
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
11-bit header CRC value.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> crc = calculate_header_crc(0, 0, 0, 0, 0, 100, 5)
|
|
46
|
+
>>> print(f"Header CRC: 0x{crc:03X}")
|
|
47
|
+
"""
|
|
48
|
+
# Combine header fields (29 bits without CRC and cycle count)
|
|
49
|
+
# Bit positions from MSB:
|
|
50
|
+
# [28] reserved
|
|
51
|
+
# [27] payload_preamble
|
|
52
|
+
# [26] null_frame
|
|
53
|
+
# [25] sync_frame
|
|
54
|
+
# [24] startup_frame
|
|
55
|
+
# [23:13] frame_id (11 bits)
|
|
56
|
+
# [12:6] payload_length (7 bits)
|
|
57
|
+
# [5:0] padding (will be shifted out during calculation)
|
|
58
|
+
data = (
|
|
59
|
+
(reserved << 28)
|
|
60
|
+
| (payload_preamble << 27)
|
|
61
|
+
| (null_frame << 26)
|
|
62
|
+
| (sync_frame << 25)
|
|
63
|
+
| (startup_frame << 24)
|
|
64
|
+
| (frame_id << 13)
|
|
65
|
+
| (payload_length << 6)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# CRC-11 calculation
|
|
69
|
+
crc = 0x01A # Initial value
|
|
70
|
+
polynomial = 0x385 # x^11 + x^9 + x^8 + x^7 + x^2 + 1
|
|
71
|
+
|
|
72
|
+
# Process 29 data bits
|
|
73
|
+
for i in range(29):
|
|
74
|
+
bit = (data >> (28 - i)) & 1
|
|
75
|
+
msb = (crc >> 10) & 1
|
|
76
|
+
|
|
77
|
+
crc = (crc << 1) & 0x7FF # Shift and keep 11 bits
|
|
78
|
+
if msb ^ bit:
|
|
79
|
+
crc ^= polynomial
|
|
80
|
+
|
|
81
|
+
return crc & 0x7FF
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def calculate_frame_crc(header: bytes, payload: bytes) -> int:
|
|
85
|
+
"""Calculate 24-bit FlexRay frame CRC.
|
|
86
|
+
|
|
87
|
+
The frame CRC protects the entire frame (header + payload).
|
|
88
|
+
|
|
89
|
+
CRC polynomial: x^24 + x^22 + x^20 + x^19 + x^18 + x^16 + x^14 +
|
|
90
|
+
x^13 + x^11 + x^10 + x^8 + x^7 + x^6 + x^3 + x^1 + 1
|
|
91
|
+
(0x5D6DCB)
|
|
92
|
+
Initial value: 0xFEDCBA
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
header: Frame header bytes (5 bytes).
|
|
96
|
+
payload: Frame payload bytes (0-254 bytes).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
24-bit frame CRC value.
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> header = bytes([0x00, 0x64, 0x12, 0x34, 0x05])
|
|
103
|
+
>>> payload = bytes([0x01, 0x02, 0x03, 0x04, 0x05])
|
|
104
|
+
>>> crc = calculate_frame_crc(header, payload)
|
|
105
|
+
>>> print(f"Frame CRC: 0x{crc:06X}")
|
|
106
|
+
"""
|
|
107
|
+
data = header + payload
|
|
108
|
+
|
|
109
|
+
crc = 0xFEDCBA # Initial value
|
|
110
|
+
polynomial = 0x5D6DCB # FlexRay CRC-24 polynomial
|
|
111
|
+
|
|
112
|
+
for byte in data:
|
|
113
|
+
crc ^= byte << 16
|
|
114
|
+
|
|
115
|
+
for _ in range(8):
|
|
116
|
+
if crc & 0x800000:
|
|
117
|
+
crc = ((crc << 1) ^ polynomial) & 0xFFFFFF
|
|
118
|
+
else:
|
|
119
|
+
crc = (crc << 1) & 0xFFFFFF
|
|
120
|
+
|
|
121
|
+
return crc
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def verify_header_crc(
|
|
125
|
+
reserved: int,
|
|
126
|
+
payload_preamble: int,
|
|
127
|
+
null_frame: int,
|
|
128
|
+
sync_frame: int,
|
|
129
|
+
startup_frame: int,
|
|
130
|
+
frame_id: int,
|
|
131
|
+
payload_length: int,
|
|
132
|
+
received_crc: int,
|
|
133
|
+
) -> bool:
|
|
134
|
+
"""Verify FlexRay header CRC.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
reserved: Reserved bit.
|
|
138
|
+
payload_preamble: Payload preamble indicator.
|
|
139
|
+
null_frame: Null frame indicator.
|
|
140
|
+
sync_frame: Sync frame indicator.
|
|
141
|
+
startup_frame: Startup frame indicator.
|
|
142
|
+
frame_id: Frame ID (11 bits).
|
|
143
|
+
payload_length: Payload length in words (7 bits).
|
|
144
|
+
received_crc: Received CRC value to verify.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if CRC is valid, False otherwise.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
>>> valid = verify_header_crc(0, 0, 0, 0, 0, 100, 5, 0x3A5)
|
|
151
|
+
>>> print(f"Header CRC valid: {valid}")
|
|
152
|
+
"""
|
|
153
|
+
computed_crc = calculate_header_crc(
|
|
154
|
+
reserved, payload_preamble, null_frame, sync_frame, startup_frame, frame_id, payload_length
|
|
155
|
+
)
|
|
156
|
+
return computed_crc == received_crc
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def verify_frame_crc(header: bytes, payload: bytes, received_crc: int) -> bool:
|
|
160
|
+
"""Verify FlexRay frame CRC.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
header: Frame header bytes (5 bytes).
|
|
164
|
+
payload: Frame payload bytes.
|
|
165
|
+
received_crc: Received CRC value to verify.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
True if CRC is valid, False otherwise.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
>>> header = bytes([0x00, 0x64, 0x12, 0x34, 0x05])
|
|
172
|
+
>>> payload = bytes([0x01, 0x02, 0x03, 0x04, 0x05])
|
|
173
|
+
>>> valid = verify_frame_crc(header, payload, 0x123456)
|
|
174
|
+
>>> print(f"Frame CRC valid: {valid}")
|
|
175
|
+
"""
|
|
176
|
+
computed_crc = calculate_frame_crc(header, payload)
|
|
177
|
+
return computed_crc == received_crc
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
__all__ = [
|
|
181
|
+
"calculate_frame_crc",
|
|
182
|
+
"calculate_header_crc",
|
|
183
|
+
"verify_frame_crc",
|
|
184
|
+
"verify_header_crc",
|
|
185
|
+
]
|