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
|
@@ -142,11 +142,11 @@ class CANDecoder(AsyncDecoder):
|
|
|
142
142
|
desc = "CAN 2.0A/B bus decoder"
|
|
143
143
|
license = "MIT"
|
|
144
144
|
|
|
145
|
-
channels = [
|
|
145
|
+
channels = [
|
|
146
146
|
ChannelDef("can", "CAN", "CAN bus signal (CAN_H - CAN_L or single-ended)"),
|
|
147
147
|
]
|
|
148
148
|
|
|
149
|
-
options = [
|
|
149
|
+
options = [
|
|
150
150
|
OptionDef(
|
|
151
151
|
"bitrate",
|
|
152
152
|
"Bit Rate",
|
|
@@ -273,7 +273,7 @@ class CANDecoder(AsyncDecoder):
|
|
|
273
273
|
data: NDArray[np.bool_],
|
|
274
274
|
samples_per_bit: int,
|
|
275
275
|
) -> list[int]:
|
|
276
|
-
"""Find potential frame start positions.
|
|
276
|
+
"""Find potential frame start positions using vectorized edge detection.
|
|
277
277
|
|
|
278
278
|
CAN frames start with a Start of Frame (SOF) bit, which is a
|
|
279
279
|
dominant (0) bit following bus idle (recessive/1).
|
|
@@ -285,26 +285,37 @@ class CANDecoder(AsyncDecoder):
|
|
|
285
285
|
Returns:
|
|
286
286
|
List of sample indices for potential frame starts.
|
|
287
287
|
"""
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
# Look for falling edges (1 -> 0) after idle period
|
|
288
|
+
# Optimize using vectorized operations instead of loop
|
|
291
289
|
min_idle_bits = 3 # Minimum idle time before frame
|
|
292
290
|
min_idle_samples = min_idle_bits * samples_per_bit
|
|
293
291
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
292
|
+
# Detect all falling edges (1 -> 0) using vectorized comparison
|
|
293
|
+
falling_edges = np.where(data[:-1] & ~data[1:])[0] + 1
|
|
294
|
+
|
|
295
|
+
frame_starts = []
|
|
296
|
+
|
|
297
|
+
# Check each falling edge for idle condition
|
|
298
|
+
for edge_idx in falling_edges:
|
|
299
|
+
if edge_idx < min_idle_samples or edge_idx >= len(data) - samples_per_bit:
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
# Check if previous samples are mostly high (idle) using vectorized mean
|
|
303
|
+
idle_region = data[edge_idx - min_idle_samples : edge_idx]
|
|
298
304
|
if np.mean(idle_region) > 0.8: # Mostly recessive
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
frame_starts.append(int(edge_idx))
|
|
306
|
+
|
|
307
|
+
# Filter out closely spaced detections (same frame)
|
|
308
|
+
if not frame_starts:
|
|
309
|
+
return []
|
|
310
|
+
|
|
311
|
+
filtered_starts = [frame_starts[0]]
|
|
312
|
+
min_frame_gap = samples_per_bit * 20 # Minimum gap between frames
|
|
306
313
|
|
|
307
|
-
|
|
314
|
+
for start in frame_starts[1:]:
|
|
315
|
+
if start - filtered_starts[-1] >= min_frame_gap:
|
|
316
|
+
filtered_starts.append(start)
|
|
317
|
+
|
|
318
|
+
return filtered_starts
|
|
308
319
|
|
|
309
320
|
def _decode_frame(
|
|
310
321
|
self,
|
|
@@ -407,127 +418,34 @@ class CANDecoder(AsyncDecoder):
|
|
|
407
418
|
Returns:
|
|
408
419
|
Parsed CANFrame or None if invalid.
|
|
409
420
|
"""
|
|
410
|
-
errors = []
|
|
421
|
+
errors: list[str] = []
|
|
411
422
|
|
|
412
423
|
try:
|
|
413
|
-
pos =
|
|
414
|
-
|
|
415
|
-
# SOF (should be 0)
|
|
416
|
-
if pos >= len(bits):
|
|
424
|
+
pos = self._parse_sof(bits, errors)
|
|
425
|
+
if pos is None:
|
|
417
426
|
return None
|
|
418
|
-
sof = bits[pos]
|
|
419
|
-
pos += 1
|
|
420
|
-
|
|
421
|
-
if sof != 0:
|
|
422
|
-
errors.append("Invalid SOF")
|
|
423
427
|
|
|
424
|
-
|
|
425
|
-
if
|
|
428
|
+
arb_result = self._parse_arbitration_field(bits, pos)
|
|
429
|
+
if arb_result[0] is None:
|
|
426
430
|
return None
|
|
431
|
+
arb_id, is_extended, is_remote, pos = arb_result
|
|
427
432
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
for i in range(11):
|
|
431
|
-
arb_id = (arb_id << 1) | bits[pos + i]
|
|
432
|
-
pos += 11
|
|
433
|
-
|
|
434
|
-
# RTR bit (for standard) or SRR bit (for extended)
|
|
435
|
-
if pos >= len(bits):
|
|
433
|
+
dlc_result = self._parse_dlc(bits, pos)
|
|
434
|
+
if dlc_result[0] is None:
|
|
436
435
|
return None
|
|
437
|
-
|
|
438
|
-
pos += 1
|
|
436
|
+
dlc, data_len, pos = dlc_result
|
|
439
437
|
|
|
440
|
-
|
|
441
|
-
if
|
|
442
|
-
return None
|
|
443
|
-
ide = bits[pos]
|
|
444
|
-
pos += 1
|
|
445
|
-
|
|
446
|
-
is_extended = bool(ide)
|
|
447
|
-
is_remote = False
|
|
448
|
-
|
|
449
|
-
if is_extended:
|
|
450
|
-
# Extended frame: 18 more ID bits
|
|
451
|
-
if pos + 18 > len(bits):
|
|
452
|
-
return None
|
|
453
|
-
|
|
454
|
-
# ID extension (18 bits)
|
|
455
|
-
for i in range(18):
|
|
456
|
-
arb_id = (arb_id << 1) | bits[pos + i]
|
|
457
|
-
pos += 18
|
|
458
|
-
|
|
459
|
-
# RTR bit
|
|
460
|
-
if pos >= len(bits):
|
|
461
|
-
return None
|
|
462
|
-
is_remote = bool(bits[pos])
|
|
463
|
-
pos += 1
|
|
464
|
-
|
|
465
|
-
# r1, r0 reserved bits
|
|
466
|
-
pos += 2
|
|
467
|
-
else:
|
|
468
|
-
# Standard frame
|
|
469
|
-
is_remote = bool(rtr_or_srr)
|
|
470
|
-
# r0 reserved bit
|
|
471
|
-
pos += 1
|
|
472
|
-
|
|
473
|
-
# DLC (4 bits)
|
|
474
|
-
if pos + 4 > len(bits):
|
|
438
|
+
data_result = self._parse_data_field(bits, pos, data_len, is_remote)
|
|
439
|
+
if data_result[0] is None:
|
|
475
440
|
return None
|
|
441
|
+
data, pos = data_result
|
|
476
442
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
dlc = (dlc << 1) | bits[pos + i]
|
|
480
|
-
pos += 4
|
|
481
|
-
|
|
482
|
-
# Limit DLC to 8
|
|
483
|
-
data_len = min(dlc, 8)
|
|
484
|
-
|
|
485
|
-
# Data field (0-8 bytes)
|
|
486
|
-
if not is_remote:
|
|
487
|
-
if pos + data_len * 8 > len(bits):
|
|
488
|
-
return None
|
|
489
|
-
|
|
490
|
-
data_bytes = bytearray()
|
|
491
|
-
for byte_idx in range(data_len):
|
|
492
|
-
byte_val = 0
|
|
493
|
-
for bit_idx in range(8):
|
|
494
|
-
byte_val = (byte_val << 1) | bits[pos + byte_idx * 8 + bit_idx + bit_idx]
|
|
495
|
-
data_bytes.append(byte_val)
|
|
496
|
-
pos += 8
|
|
497
|
-
|
|
498
|
-
data = bytes(data_bytes)
|
|
499
|
-
else:
|
|
500
|
-
data = b""
|
|
501
|
-
|
|
502
|
-
# CRC field (15 bits)
|
|
503
|
-
if pos + 15 > len(bits):
|
|
443
|
+
crc_result = self._parse_crc_field(bits, pos, errors)
|
|
444
|
+
if crc_result[0] is None:
|
|
504
445
|
return None
|
|
446
|
+
crc_received, crc_computed, pos = crc_result
|
|
505
447
|
|
|
506
|
-
|
|
507
|
-
for i in range(15):
|
|
508
|
-
crc_received = (crc_received << 1) | bits[pos + i]
|
|
509
|
-
pos += 15
|
|
510
|
-
|
|
511
|
-
# Compute CRC on frame bits before CRC field
|
|
512
|
-
# CRC covers SOF through data field
|
|
513
|
-
crc_data_end = pos - 15
|
|
514
|
-
crc_computed = self._compute_crc(bits[:crc_data_end])
|
|
515
|
-
|
|
516
|
-
if crc_received != crc_computed:
|
|
517
|
-
errors.append(
|
|
518
|
-
f"CRC error: received 0x{crc_received:04X}, computed 0x{crc_computed:04X}"
|
|
519
|
-
)
|
|
520
|
-
|
|
521
|
-
# CRC delimiter (should be 1)
|
|
522
|
-
if pos < len(bits) and bits[pos] != 1:
|
|
523
|
-
errors.append("CRC delimiter error")
|
|
524
|
-
pos += 1
|
|
525
|
-
|
|
526
|
-
# ACK slot and delimiter
|
|
527
|
-
pos += 2
|
|
528
|
-
|
|
529
|
-
# EOF (7 recessive bits)
|
|
530
|
-
# We don't strictly check this
|
|
448
|
+
pos = self._parse_ack_eof(bits, pos, errors)
|
|
531
449
|
|
|
532
450
|
end_time = start_time + pos * (1.0 / self._bitrate)
|
|
533
451
|
|
|
@@ -547,6 +465,218 @@ class CANDecoder(AsyncDecoder):
|
|
|
547
465
|
except (IndexError, ValueError):
|
|
548
466
|
return None
|
|
549
467
|
|
|
468
|
+
def _parse_sof(self, bits: list[int], errors: list[str]) -> int | None:
|
|
469
|
+
"""Parse Start of Frame bit.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
bits: Bit array.
|
|
473
|
+
errors: Error list to append to.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Position after SOF, or None if invalid.
|
|
477
|
+
"""
|
|
478
|
+
if len(bits) < 1:
|
|
479
|
+
return None
|
|
480
|
+
|
|
481
|
+
sof = bits[0]
|
|
482
|
+
if sof != 0:
|
|
483
|
+
errors.append("Invalid SOF")
|
|
484
|
+
|
|
485
|
+
return 1
|
|
486
|
+
|
|
487
|
+
def _parse_arbitration_field(
|
|
488
|
+
self, bits: list[int], pos: int
|
|
489
|
+
) -> tuple[int, bool, bool, int] | tuple[None, None, None, None]:
|
|
490
|
+
"""Parse CAN arbitration field (ID, RTR, IDE).
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
bits: Bit array.
|
|
494
|
+
pos: Current position.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
Tuple of (arb_id, is_extended, is_remote, new_pos) or (None, None, None, None).
|
|
498
|
+
"""
|
|
499
|
+
if pos + 11 > len(bits):
|
|
500
|
+
return None, None, None, None
|
|
501
|
+
|
|
502
|
+
# First 11 bits of ID
|
|
503
|
+
arb_id = self._extract_bits_as_int(bits, pos, 11)
|
|
504
|
+
pos += 11
|
|
505
|
+
|
|
506
|
+
# RTR/SRR bit
|
|
507
|
+
if pos >= len(bits):
|
|
508
|
+
return None, None, None, None
|
|
509
|
+
rtr_or_srr = bits[pos]
|
|
510
|
+
pos += 1
|
|
511
|
+
|
|
512
|
+
# IDE bit
|
|
513
|
+
if pos >= len(bits):
|
|
514
|
+
return None, None, None, None
|
|
515
|
+
ide = bits[pos]
|
|
516
|
+
pos += 1
|
|
517
|
+
|
|
518
|
+
is_extended = bool(ide)
|
|
519
|
+
|
|
520
|
+
if is_extended:
|
|
521
|
+
try:
|
|
522
|
+
arb_id, is_remote, pos = self._parse_extended_id(bits, pos, arb_id)
|
|
523
|
+
except IndexError:
|
|
524
|
+
return None, None, None, None
|
|
525
|
+
else:
|
|
526
|
+
is_remote = bool(rtr_or_srr)
|
|
527
|
+
pos += 1 # r0 reserved bit
|
|
528
|
+
|
|
529
|
+
return arb_id, is_extended, is_remote, pos
|
|
530
|
+
|
|
531
|
+
def _parse_extended_id(self, bits: list[int], pos: int, base_id: int) -> tuple[int, bool, int]:
|
|
532
|
+
"""Parse extended CAN ID (18 additional bits).
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
bits: Bit array.
|
|
536
|
+
pos: Current position.
|
|
537
|
+
base_id: Base 11-bit ID.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
Tuple of (full_id, is_remote, new_pos).
|
|
541
|
+
|
|
542
|
+
Raises:
|
|
543
|
+
IndexError: If insufficient bits available.
|
|
544
|
+
"""
|
|
545
|
+
if pos + 18 > len(bits):
|
|
546
|
+
raise IndexError("Insufficient bits for extended ID")
|
|
547
|
+
|
|
548
|
+
# ID extension (18 bits)
|
|
549
|
+
id_ext = self._extract_bits_as_int(bits, pos, 18)
|
|
550
|
+
arb_id = (base_id << 18) | id_ext
|
|
551
|
+
pos += 18
|
|
552
|
+
|
|
553
|
+
# RTR bit
|
|
554
|
+
if pos >= len(bits):
|
|
555
|
+
raise IndexError("Insufficient bits for RTR")
|
|
556
|
+
is_remote = bool(bits[pos])
|
|
557
|
+
pos += 1
|
|
558
|
+
|
|
559
|
+
# r1, r0 reserved bits
|
|
560
|
+
pos += 2
|
|
561
|
+
|
|
562
|
+
return arb_id, is_remote, pos
|
|
563
|
+
|
|
564
|
+
def _parse_dlc(
|
|
565
|
+
self, bits: list[int], pos: int
|
|
566
|
+
) -> tuple[int, int, int] | tuple[None, None, None]:
|
|
567
|
+
"""Parse Data Length Code.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
bits: Bit array.
|
|
571
|
+
pos: Current position.
|
|
572
|
+
|
|
573
|
+
Returns:
|
|
574
|
+
Tuple of (dlc, data_len, new_pos) or (None, None, None).
|
|
575
|
+
"""
|
|
576
|
+
if pos + 4 > len(bits):
|
|
577
|
+
return None, None, None
|
|
578
|
+
|
|
579
|
+
dlc = self._extract_bits_as_int(bits, pos, 4)
|
|
580
|
+
pos += 4
|
|
581
|
+
|
|
582
|
+
data_len = min(dlc, 8) # Limit to 8 bytes
|
|
583
|
+
return dlc, data_len, pos
|
|
584
|
+
|
|
585
|
+
def _parse_data_field(
|
|
586
|
+
self, bits: list[int], pos: int, data_len: int, is_remote: bool
|
|
587
|
+
) -> tuple[bytes, int] | tuple[None, None]:
|
|
588
|
+
"""Parse CAN data field.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
bits: Bit array.
|
|
592
|
+
pos: Current position.
|
|
593
|
+
data_len: Number of data bytes.
|
|
594
|
+
is_remote: True if remote frame.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
Tuple of (data_bytes, new_pos) or (None, None).
|
|
598
|
+
"""
|
|
599
|
+
if is_remote:
|
|
600
|
+
return b"", pos
|
|
601
|
+
|
|
602
|
+
if pos + data_len * 8 > len(bits):
|
|
603
|
+
return None, None
|
|
604
|
+
|
|
605
|
+
data_bytes = bytearray()
|
|
606
|
+
for _ in range(data_len):
|
|
607
|
+
byte_val = self._extract_bits_as_int(bits, pos, 8)
|
|
608
|
+
data_bytes.append(byte_val)
|
|
609
|
+
pos += 8
|
|
610
|
+
|
|
611
|
+
return bytes(data_bytes), pos
|
|
612
|
+
|
|
613
|
+
def _parse_crc_field(
|
|
614
|
+
self, bits: list[int], pos: int, errors: list[str]
|
|
615
|
+
) -> tuple[int, int, int] | tuple[None, None, None]:
|
|
616
|
+
"""Parse CRC field and validate.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
bits: Bit array.
|
|
620
|
+
pos: Current position.
|
|
621
|
+
errors: Error list to append to.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
Tuple of (crc_received, crc_computed, new_pos) or (None, None, None).
|
|
625
|
+
"""
|
|
626
|
+
if pos + 15 > len(bits):
|
|
627
|
+
return None, None, None
|
|
628
|
+
|
|
629
|
+
crc_received = self._extract_bits_as_int(bits, pos, 15)
|
|
630
|
+
crc_data_end = pos
|
|
631
|
+
pos += 15
|
|
632
|
+
|
|
633
|
+
crc_computed = self._compute_crc(bits[:crc_data_end])
|
|
634
|
+
|
|
635
|
+
if crc_received != crc_computed:
|
|
636
|
+
errors.append(
|
|
637
|
+
f"CRC error: received 0x{crc_received:04X}, computed 0x{crc_computed:04X}"
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
# CRC delimiter (should be 1)
|
|
641
|
+
if pos < len(bits) and bits[pos] != 1:
|
|
642
|
+
errors.append("CRC delimiter error")
|
|
643
|
+
pos += 1
|
|
644
|
+
|
|
645
|
+
return crc_received, crc_computed, pos
|
|
646
|
+
|
|
647
|
+
def _parse_ack_eof(self, bits: list[int], pos: int, errors: list[str]) -> int:
|
|
648
|
+
"""Parse ACK and EOF fields.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
bits: Bit array.
|
|
652
|
+
pos: Current position.
|
|
653
|
+
errors: Error list to append to.
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
Position after ACK/EOF.
|
|
657
|
+
"""
|
|
658
|
+
# ACK slot and delimiter
|
|
659
|
+
pos += 2
|
|
660
|
+
|
|
661
|
+
# EOF (7 recessive bits) - not strictly checked
|
|
662
|
+
return pos
|
|
663
|
+
|
|
664
|
+
def _extract_bits_as_int(self, bits: list[int], start: int, count: int) -> int:
|
|
665
|
+
"""Extract consecutive bits as integer (MSB first).
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
bits: Bit array.
|
|
669
|
+
start: Start position.
|
|
670
|
+
count: Number of bits.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
Integer value.
|
|
674
|
+
"""
|
|
675
|
+
value = 0
|
|
676
|
+
for i in range(count):
|
|
677
|
+
value = (value << 1) | bits[start + i]
|
|
678
|
+
return value
|
|
679
|
+
|
|
550
680
|
def _compute_crc(self, bits: list[int]) -> int:
|
|
551
681
|
"""Compute CAN CRC-15.
|
|
552
682
|
|
|
@@ -120,16 +120,16 @@ class CANFDDecoder(AsyncDecoder):
|
|
|
120
120
|
longname = "CAN with Flexible Data-rate"
|
|
121
121
|
desc = "CAN-FD protocol decoder"
|
|
122
122
|
|
|
123
|
-
channels = [
|
|
123
|
+
channels = [
|
|
124
124
|
ChannelDef("can", "CAN", "CAN bus signal", required=True),
|
|
125
125
|
]
|
|
126
126
|
|
|
127
|
-
optional_channels = [
|
|
127
|
+
optional_channels = [
|
|
128
128
|
ChannelDef("can_h", "CAN_H", "CAN High differential signal", required=False),
|
|
129
129
|
ChannelDef("can_l", "CAN_L", "CAN Low differential signal", required=False),
|
|
130
130
|
]
|
|
131
131
|
|
|
132
|
-
options = [
|
|
132
|
+
options = [
|
|
133
133
|
OptionDef(
|
|
134
134
|
"nominal_bitrate",
|
|
135
135
|
"Nominal bitrate",
|
|
@@ -146,7 +146,7 @@ class CANFDDecoder(AsyncDecoder):
|
|
|
146
146
|
),
|
|
147
147
|
]
|
|
148
148
|
|
|
149
|
-
annotations = [
|
|
149
|
+
annotations = [
|
|
150
150
|
("sof", "Start of Frame"),
|
|
151
151
|
("arbitration", "Arbitration field"),
|
|
152
152
|
("control", "Control field"),
|
|
@@ -299,111 +299,138 @@ class CANFDDecoder(AsyncDecoder):
|
|
|
299
299
|
Returns:
|
|
300
300
|
(frame, end_index) tuple.
|
|
301
301
|
"""
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
302
|
+
decoder_state = _CANFDDecoderState(sof_idx, nominal_bit_period, data, data_bit_period)
|
|
303
|
+
|
|
304
|
+
arbitration_id, is_extended = decoder_state.decode_arbitration_field()
|
|
305
|
+
if arbitration_id is None:
|
|
306
|
+
return None, decoder_state.get_bit_idx()
|
|
307
|
+
|
|
308
|
+
is_fd, brs, esi, dlc = decoder_state.decode_control_field(is_extended)
|
|
309
|
+
if dlc is None:
|
|
310
|
+
return None, decoder_state.get_bit_idx()
|
|
311
|
+
|
|
312
|
+
data_length = CANFD_DLC_TO_LENGTH.get(dlc, 0)
|
|
313
|
+
data_bytes = decoder_state.decode_data_field(data_length, is_fd, brs)
|
|
314
|
+
crc = decoder_state.decode_crc_field(data_length)
|
|
315
|
+
decoder_state.decode_end_of_frame()
|
|
316
|
+
|
|
317
|
+
frame = CANFDFrame(
|
|
318
|
+
arbitration_id=arbitration_id,
|
|
319
|
+
is_extended=is_extended,
|
|
320
|
+
is_fd=is_fd,
|
|
321
|
+
brs=brs,
|
|
322
|
+
esi=esi,
|
|
323
|
+
dlc=dlc,
|
|
324
|
+
data=bytes(data_bytes),
|
|
325
|
+
crc=crc,
|
|
326
|
+
timestamp=sof_idx / sample_rate,
|
|
327
|
+
errors=[],
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
return frame, decoder_state.get_bit_idx()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class _CANFDDecoderState:
|
|
334
|
+
"""State tracker for CAN-FD frame decoding."""
|
|
335
|
+
|
|
336
|
+
def __init__(
|
|
337
|
+
self,
|
|
338
|
+
sof_idx: int,
|
|
339
|
+
nominal_bit_period: float,
|
|
340
|
+
data: NDArray[np.bool_],
|
|
341
|
+
data_bit_period: float,
|
|
342
|
+
):
|
|
343
|
+
self.bit_idx: float = float(sof_idx)
|
|
344
|
+
self.current_bit_period = nominal_bit_period
|
|
345
|
+
self.nominal_bit_period = nominal_bit_period
|
|
346
|
+
self.data_bit_period = data_bit_period
|
|
347
|
+
self.data = data
|
|
348
|
+
|
|
349
|
+
def sample_bits(self, count: int) -> list[int]:
|
|
350
|
+
"""Sample specified number of bits."""
|
|
351
|
+
bits = []
|
|
352
|
+
for _ in range(count):
|
|
353
|
+
sample_idx_raw = self.bit_idx + self.current_bit_period / 2
|
|
354
|
+
sample_idx = int(sample_idx_raw)
|
|
355
|
+
if sample_idx < len(self.data):
|
|
356
|
+
bits.append(0 if self.data[sample_idx] else 1)
|
|
357
|
+
self.bit_idx += self.current_bit_period
|
|
358
|
+
else:
|
|
359
|
+
return bits
|
|
360
|
+
return bits
|
|
361
|
+
|
|
362
|
+
def bits_to_int(self, bits: list[int]) -> int:
|
|
363
|
+
"""Convert bit list to integer."""
|
|
364
|
+
value = 0
|
|
365
|
+
for bit in bits:
|
|
366
|
+
value = (value << 1) | bit
|
|
367
|
+
return value
|
|
368
|
+
|
|
369
|
+
def decode_arbitration_field(self) -> tuple[int | None, bool]:
|
|
370
|
+
"""Decode arbitration field and determine if extended frame."""
|
|
371
|
+
arb_bits = self.sample_bits(11)
|
|
321
372
|
if len(arb_bits) < 11:
|
|
322
|
-
return None,
|
|
373
|
+
return None, False
|
|
323
374
|
|
|
324
|
-
arbitration_id =
|
|
325
|
-
for bit in arb_bits:
|
|
326
|
-
arbitration_id = (arbitration_id << 1) | bit
|
|
375
|
+
arbitration_id = self.bits_to_int(arb_bits)
|
|
327
376
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
is_extended = ide_bits[0] == 1 if ide_bits else False
|
|
377
|
+
ide_bits = self.sample_bits(1)
|
|
378
|
+
is_extended = bool(ide_bits[0]) if ide_bits else False
|
|
331
379
|
|
|
332
380
|
if is_extended:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
381
|
+
ext_bits = self.sample_bits(18)
|
|
382
|
+
arbitration_id = (arbitration_id << 18) | self.bits_to_int(ext_bits)
|
|
383
|
+
|
|
384
|
+
return arbitration_id, is_extended
|
|
337
385
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
ctrl_bits = sample_bits(7 if not is_extended else 6)
|
|
386
|
+
def decode_control_field(self, is_extended: bool) -> tuple[bool, bool, bool, int | None]:
|
|
387
|
+
"""Decode control field."""
|
|
388
|
+
ctrl_bits = self.sample_bits(7 if not is_extended else 6)
|
|
341
389
|
|
|
342
390
|
if len(ctrl_bits) < (7 if not is_extended else 6):
|
|
343
|
-
return
|
|
391
|
+
return False, False, False, None
|
|
344
392
|
|
|
345
|
-
|
|
346
|
-
fdf = ctrl_bits[0]
|
|
347
|
-
is_fd = fdf == 1
|
|
393
|
+
is_fd = ctrl_bits[0] == 1
|
|
348
394
|
brs = ctrl_bits[2] == 1 if len(ctrl_bits) > 2 else False
|
|
349
395
|
esi = ctrl_bits[3] == 1 if len(ctrl_bits) > 3 else False
|
|
350
396
|
|
|
351
|
-
# DLC (4 bits)
|
|
352
397
|
dlc_start = 3 if not is_extended else 2
|
|
353
398
|
dlc_bits = (
|
|
354
399
|
ctrl_bits[dlc_start : dlc_start + 4]
|
|
355
400
|
if len(ctrl_bits) >= dlc_start + 4
|
|
356
401
|
else [0, 0, 0, 0]
|
|
357
402
|
)
|
|
358
|
-
dlc =
|
|
359
|
-
for bit in dlc_bits:
|
|
360
|
-
dlc = (dlc << 1) | bit
|
|
403
|
+
dlc = self.bits_to_int(dlc_bits)
|
|
361
404
|
|
|
362
|
-
|
|
363
|
-
data_length = CANFD_DLC_TO_LENGTH.get(dlc, 0)
|
|
405
|
+
return is_fd, brs, esi, dlc
|
|
364
406
|
|
|
365
|
-
|
|
407
|
+
def decode_data_field(self, data_length: int, is_fd: bool, brs: bool) -> list[int]:
|
|
408
|
+
"""Decode data field."""
|
|
366
409
|
if is_fd and brs:
|
|
367
|
-
current_bit_period = data_bit_period
|
|
410
|
+
self.current_bit_period = self.data_bit_period
|
|
368
411
|
|
|
369
|
-
# Data field
|
|
370
412
|
data_bytes = []
|
|
371
413
|
for _ in range(data_length):
|
|
372
|
-
byte_bits = sample_bits(8)
|
|
414
|
+
byte_bits = self.sample_bits(8)
|
|
373
415
|
if len(byte_bits) == 8:
|
|
374
|
-
|
|
375
|
-
for bit in byte_bits:
|
|
376
|
-
byte_val = (byte_val << 1) | bit
|
|
377
|
-
data_bytes.append(byte_val)
|
|
378
|
-
|
|
379
|
-
# CRC field (CRC-17 for <=16 bytes, CRC-21 for >16 bytes)
|
|
380
|
-
crc_length = 17 if data_length <= 16 else 21
|
|
381
|
-
crc_bits = sample_bits(crc_length)
|
|
382
|
-
crc = 0
|
|
383
|
-
for bit in crc_bits:
|
|
384
|
-
crc = (crc << 1) | bit
|
|
416
|
+
data_bytes.append(self.bits_to_int(byte_bits))
|
|
385
417
|
|
|
386
|
-
|
|
387
|
-
current_bit_period = nominal_bit_period
|
|
418
|
+
return data_bytes
|
|
388
419
|
|
|
389
|
-
|
|
390
|
-
|
|
420
|
+
def decode_crc_field(self, data_length: int) -> int:
|
|
421
|
+
"""Decode CRC field."""
|
|
422
|
+
crc_length = 17 if data_length <= 16 else 21
|
|
423
|
+
crc_bits = self.sample_bits(crc_length)
|
|
424
|
+
return self.bits_to_int(crc_bits)
|
|
391
425
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
is_fd=is_fd,
|
|
397
|
-
brs=brs,
|
|
398
|
-
esi=esi,
|
|
399
|
-
dlc=dlc,
|
|
400
|
-
data=bytes(data_bytes),
|
|
401
|
-
crc=crc,
|
|
402
|
-
timestamp=sof_idx / sample_rate,
|
|
403
|
-
errors=errors,
|
|
404
|
-
)
|
|
426
|
+
def decode_end_of_frame(self) -> None:
|
|
427
|
+
"""Decode end of frame."""
|
|
428
|
+
self.current_bit_period = self.nominal_bit_period
|
|
429
|
+
self.sample_bits(10)
|
|
405
430
|
|
|
406
|
-
|
|
431
|
+
def get_bit_idx(self) -> int:
|
|
432
|
+
"""Get current bit index."""
|
|
433
|
+
return int(self.bit_idx)
|
|
407
434
|
|
|
408
435
|
|
|
409
436
|
def decode_can_fd(
|