oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +169 -167
- oscura/analyzers/__init__.py +3 -0
- oscura/analyzers/classification.py +659 -0
- oscura/analyzers/digital/__init__.py +0 -48
- oscura/analyzers/digital/edges.py +325 -65
- oscura/analyzers/digital/extraction.py +0 -195
- oscura/analyzers/digital/quality.py +293 -166
- oscura/analyzers/digital/timing.py +260 -115
- oscura/analyzers/digital/timing_numba.py +334 -0
- oscura/analyzers/entropy.py +605 -0
- oscura/analyzers/eye/diagram.py +176 -109
- oscura/analyzers/eye/metrics.py +5 -5
- oscura/analyzers/jitter/__init__.py +6 -4
- oscura/analyzers/jitter/ber.py +52 -52
- oscura/analyzers/jitter/classification.py +156 -0
- oscura/analyzers/jitter/decomposition.py +163 -113
- oscura/analyzers/jitter/spectrum.py +80 -64
- oscura/analyzers/ml/__init__.py +39 -0
- oscura/analyzers/ml/features.py +600 -0
- oscura/analyzers/ml/signal_classifier.py +604 -0
- oscura/analyzers/packet/daq.py +246 -158
- oscura/analyzers/packet/parser.py +12 -1
- oscura/analyzers/packet/payload.py +50 -2110
- oscura/analyzers/packet/payload_analysis.py +361 -181
- oscura/analyzers/packet/payload_patterns.py +133 -70
- oscura/analyzers/packet/stream.py +84 -23
- oscura/analyzers/patterns/__init__.py +26 -5
- oscura/analyzers/patterns/anomaly_detection.py +908 -0
- oscura/analyzers/patterns/clustering.py +169 -108
- oscura/analyzers/patterns/clustering_optimized.py +227 -0
- oscura/analyzers/patterns/discovery.py +1 -1
- oscura/analyzers/patterns/matching.py +581 -197
- oscura/analyzers/patterns/pattern_mining.py +778 -0
- oscura/analyzers/patterns/periodic.py +121 -38
- oscura/analyzers/patterns/sequences.py +175 -78
- oscura/analyzers/power/conduction.py +1 -1
- oscura/analyzers/power/soa.py +6 -6
- oscura/analyzers/power/switching.py +250 -110
- oscura/analyzers/protocol/__init__.py +17 -1
- oscura/analyzers/protocols/__init__.py +1 -22
- oscura/analyzers/protocols/base.py +6 -6
- oscura/analyzers/protocols/ble/__init__.py +38 -0
- oscura/analyzers/protocols/ble/analyzer.py +809 -0
- oscura/analyzers/protocols/ble/uuids.py +288 -0
- oscura/analyzers/protocols/can.py +257 -127
- oscura/analyzers/protocols/can_fd.py +107 -80
- oscura/analyzers/protocols/flexray.py +139 -80
- oscura/analyzers/protocols/hdlc.py +93 -58
- oscura/analyzers/protocols/i2c.py +247 -106
- oscura/analyzers/protocols/i2s.py +138 -86
- oscura/analyzers/protocols/industrial/__init__.py +40 -0
- oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
- oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
- oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
- oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
- oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
- oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
- oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
- oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
- oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
- oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
- oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
- oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
- oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
- oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
- oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
- oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
- oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
- oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
- oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
- oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
- oscura/analyzers/protocols/jtag.py +180 -98
- oscura/analyzers/protocols/lin.py +219 -114
- oscura/analyzers/protocols/manchester.py +4 -4
- oscura/analyzers/protocols/onewire.py +253 -149
- oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
- oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
- oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
- oscura/analyzers/protocols/spi.py +192 -95
- oscura/analyzers/protocols/swd.py +321 -167
- oscura/analyzers/protocols/uart.py +267 -125
- oscura/analyzers/protocols/usb.py +235 -131
- oscura/analyzers/side_channel/power.py +17 -12
- oscura/analyzers/signal/__init__.py +15 -0
- oscura/analyzers/signal/timing_analysis.py +1086 -0
- oscura/analyzers/signal_integrity/__init__.py +4 -1
- oscura/analyzers/signal_integrity/sparams.py +2 -19
- oscura/analyzers/spectral/chunked.py +129 -60
- oscura/analyzers/spectral/chunked_fft.py +300 -94
- oscura/analyzers/spectral/chunked_wavelet.py +100 -80
- oscura/analyzers/statistical/checksum.py +376 -217
- oscura/analyzers/statistical/classification.py +229 -107
- oscura/analyzers/statistical/entropy.py +78 -53
- oscura/analyzers/statistics/correlation.py +407 -211
- oscura/analyzers/statistics/outliers.py +2 -2
- oscura/analyzers/statistics/streaming.py +30 -5
- oscura/analyzers/validation.py +216 -101
- oscura/analyzers/waveform/measurements.py +9 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
- oscura/analyzers/waveform/spectral.py +500 -228
- oscura/api/__init__.py +31 -5
- oscura/api/dsl/__init__.py +582 -0
- oscura/{dsl → api/dsl}/commands.py +43 -76
- oscura/{dsl → api/dsl}/interpreter.py +26 -51
- oscura/{dsl → api/dsl}/parser.py +107 -77
- oscura/{dsl → api/dsl}/repl.py +2 -2
- oscura/api/dsl.py +1 -1
- oscura/{integrations → api/integrations}/__init__.py +1 -1
- oscura/{integrations → api/integrations}/llm.py +201 -102
- oscura/api/operators.py +3 -3
- oscura/api/optimization.py +144 -30
- oscura/api/rest_server.py +921 -0
- oscura/api/server/__init__.py +17 -0
- oscura/api/server/dashboard.py +850 -0
- oscura/api/server/static/README.md +34 -0
- oscura/api/server/templates/base.html +181 -0
- oscura/api/server/templates/export.html +120 -0
- oscura/api/server/templates/home.html +284 -0
- oscura/api/server/templates/protocols.html +58 -0
- oscura/api/server/templates/reports.html +43 -0
- oscura/api/server/templates/session_detail.html +89 -0
- oscura/api/server/templates/sessions.html +83 -0
- oscura/api/server/templates/waveforms.html +73 -0
- oscura/automotive/__init__.py +8 -1
- oscura/automotive/can/__init__.py +10 -0
- oscura/automotive/can/checksum.py +3 -1
- oscura/automotive/can/dbc_generator.py +590 -0
- oscura/automotive/can/message_wrapper.py +121 -74
- oscura/automotive/can/patterns.py +98 -21
- oscura/automotive/can/session.py +292 -56
- oscura/automotive/can/state_machine.py +6 -3
- oscura/automotive/can/stimulus_response.py +97 -75
- oscura/automotive/dbc/__init__.py +10 -2
- oscura/automotive/dbc/generator.py +84 -56
- oscura/automotive/dbc/parser.py +6 -6
- oscura/automotive/dtc/data.json +2763 -0
- oscura/automotive/dtc/database.py +2 -2
- oscura/automotive/flexray/__init__.py +31 -0
- oscura/automotive/flexray/analyzer.py +504 -0
- oscura/automotive/flexray/crc.py +185 -0
- oscura/automotive/flexray/fibex.py +449 -0
- oscura/automotive/j1939/__init__.py +45 -8
- oscura/automotive/j1939/analyzer.py +605 -0
- oscura/automotive/j1939/spns.py +326 -0
- oscura/automotive/j1939/transport.py +306 -0
- oscura/automotive/lin/__init__.py +47 -0
- oscura/automotive/lin/analyzer.py +612 -0
- oscura/automotive/loaders/blf.py +13 -2
- oscura/automotive/loaders/csv_can.py +143 -72
- oscura/automotive/loaders/dispatcher.py +50 -2
- oscura/automotive/loaders/mdf.py +86 -45
- oscura/automotive/loaders/pcap.py +111 -61
- oscura/automotive/uds/__init__.py +4 -0
- oscura/automotive/uds/analyzer.py +725 -0
- oscura/automotive/uds/decoder.py +140 -58
- oscura/automotive/uds/models.py +7 -1
- oscura/automotive/visualization.py +1 -1
- oscura/cli/analyze.py +348 -0
- oscura/cli/batch.py +142 -122
- oscura/cli/benchmark.py +275 -0
- oscura/cli/characterize.py +137 -82
- oscura/cli/compare.py +224 -131
- oscura/cli/completion.py +250 -0
- oscura/cli/config_cmd.py +361 -0
- oscura/cli/decode.py +164 -87
- oscura/cli/export.py +286 -0
- oscura/cli/main.py +115 -31
- oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
- oscura/{onboarding → cli/onboarding}/help.py +80 -58
- oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
- oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
- oscura/cli/progress.py +147 -0
- oscura/cli/shell.py +157 -135
- oscura/cli/validate_cmd.py +204 -0
- oscura/cli/visualize.py +158 -0
- oscura/convenience.py +125 -79
- oscura/core/__init__.py +4 -2
- oscura/core/backend_selector.py +3 -3
- oscura/core/cache.py +126 -15
- oscura/core/cancellation.py +1 -1
- oscura/{config → core/config}/__init__.py +20 -11
- oscura/{config → core/config}/defaults.py +1 -1
- oscura/{config → core/config}/loader.py +7 -5
- oscura/{config → core/config}/memory.py +5 -5
- oscura/{config → core/config}/migration.py +1 -1
- oscura/{config → core/config}/pipeline.py +99 -23
- oscura/{config → core/config}/preferences.py +1 -1
- oscura/{config → core/config}/protocol.py +3 -3
- oscura/{config → core/config}/schema.py +426 -272
- oscura/{config → core/config}/settings.py +1 -1
- oscura/{config → core/config}/thresholds.py +195 -153
- oscura/core/correlation.py +5 -6
- oscura/core/cross_domain.py +0 -2
- oscura/core/debug.py +9 -5
- oscura/{extensibility → core/extensibility}/docs.py +158 -70
- oscura/{extensibility → core/extensibility}/extensions.py +160 -76
- oscura/{extensibility → core/extensibility}/logging.py +1 -1
- oscura/{extensibility → core/extensibility}/measurements.py +1 -1
- oscura/{extensibility → core/extensibility}/plugins.py +1 -1
- oscura/{extensibility → core/extensibility}/templates.py +73 -3
- oscura/{extensibility → core/extensibility}/validation.py +1 -1
- oscura/core/gpu_backend.py +11 -7
- oscura/core/log_query.py +101 -11
- oscura/core/logging.py +126 -54
- oscura/core/logging_advanced.py +5 -5
- oscura/core/memory_limits.py +108 -70
- oscura/core/memory_monitor.py +2 -2
- oscura/core/memory_progress.py +7 -7
- oscura/core/memory_warnings.py +1 -1
- oscura/core/numba_backend.py +13 -13
- oscura/{plugins → core/plugins}/__init__.py +9 -9
- oscura/{plugins → core/plugins}/base.py +7 -7
- oscura/{plugins → core/plugins}/cli.py +3 -3
- oscura/{plugins → core/plugins}/discovery.py +186 -106
- oscura/{plugins → core/plugins}/lifecycle.py +1 -1
- oscura/{plugins → core/plugins}/manager.py +7 -7
- oscura/{plugins → core/plugins}/registry.py +3 -3
- oscura/{plugins → core/plugins}/versioning.py +1 -1
- oscura/core/progress.py +16 -1
- oscura/core/provenance.py +8 -2
- oscura/{schemas → core/schemas}/__init__.py +2 -2
- oscura/core/schemas/bus_configuration.json +322 -0
- oscura/core/schemas/device_mapping.json +182 -0
- oscura/core/schemas/packet_format.json +418 -0
- oscura/core/schemas/protocol_definition.json +363 -0
- oscura/core/types.py +4 -0
- oscura/core/uncertainty.py +3 -3
- oscura/correlation/__init__.py +52 -0
- oscura/correlation/multi_protocol.py +811 -0
- oscura/discovery/auto_decoder.py +117 -35
- oscura/discovery/comparison.py +191 -86
- oscura/discovery/quality_validator.py +155 -68
- oscura/discovery/signal_detector.py +196 -79
- oscura/export/__init__.py +18 -20
- oscura/export/kaitai_struct.py +513 -0
- oscura/export/scapy_layer.py +801 -0
- oscura/export/wireshark/README.md +15 -15
- oscura/export/wireshark/generator.py +1 -1
- oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
- oscura/export/wireshark_dissector.py +746 -0
- oscura/guidance/wizard.py +207 -111
- oscura/hardware/__init__.py +19 -0
- oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
- oscura/{acquisition → hardware/acquisition}/file.py +2 -2
- oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
- oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
- oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
- oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
- oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
- oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
- oscura/hardware/firmware/__init__.py +29 -0
- oscura/hardware/firmware/pattern_recognition.py +874 -0
- oscura/hardware/hal_detector.py +736 -0
- oscura/hardware/security/__init__.py +37 -0
- oscura/hardware/security/side_channel_detector.py +1126 -0
- oscura/inference/__init__.py +4 -0
- oscura/inference/active_learning/README.md +7 -7
- oscura/inference/active_learning/observation_table.py +4 -1
- oscura/inference/alignment.py +216 -123
- oscura/inference/bayesian.py +113 -33
- oscura/inference/crc_reverse.py +101 -55
- oscura/inference/logic.py +6 -2
- oscura/inference/message_format.py +342 -183
- oscura/inference/protocol.py +95 -44
- oscura/inference/protocol_dsl.py +180 -82
- oscura/inference/signal_intelligence.py +1439 -706
- oscura/inference/spectral.py +99 -57
- oscura/inference/state_machine.py +810 -158
- oscura/inference/stream.py +270 -110
- oscura/iot/__init__.py +34 -0
- oscura/iot/coap/__init__.py +32 -0
- oscura/iot/coap/analyzer.py +668 -0
- oscura/iot/coap/options.py +212 -0
- oscura/iot/lorawan/__init__.py +21 -0
- oscura/iot/lorawan/crypto.py +206 -0
- oscura/iot/lorawan/decoder.py +801 -0
- oscura/iot/lorawan/mac_commands.py +341 -0
- oscura/iot/mqtt/__init__.py +27 -0
- oscura/iot/mqtt/analyzer.py +999 -0
- oscura/iot/mqtt/properties.py +315 -0
- oscura/iot/zigbee/__init__.py +31 -0
- oscura/iot/zigbee/analyzer.py +615 -0
- oscura/iot/zigbee/security.py +153 -0
- oscura/iot/zigbee/zcl.py +349 -0
- oscura/jupyter/display.py +125 -45
- oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
- oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
- oscura/jupyter/exploratory/fuzzy.py +746 -0
- oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
- oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
- oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
- oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
- oscura/jupyter/exploratory/sync.py +612 -0
- oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
- oscura/jupyter/magic.py +4 -4
- oscura/{ui → jupyter/ui}/__init__.py +2 -2
- oscura/{ui → jupyter/ui}/formatters.py +3 -3
- oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
- oscura/loaders/__init__.py +171 -63
- oscura/loaders/binary.py +88 -1
- oscura/loaders/chipwhisperer.py +153 -137
- oscura/loaders/configurable.py +208 -86
- oscura/loaders/csv_loader.py +458 -215
- oscura/loaders/hdf5_loader.py +278 -119
- oscura/loaders/lazy.py +87 -54
- oscura/loaders/mmap_loader.py +1 -1
- oscura/loaders/numpy_loader.py +253 -116
- oscura/loaders/pcap.py +226 -151
- oscura/loaders/rigol.py +110 -49
- oscura/loaders/sigrok.py +201 -78
- oscura/loaders/tdms.py +81 -58
- oscura/loaders/tektronix.py +291 -174
- oscura/loaders/touchstone.py +182 -87
- oscura/loaders/vcd.py +215 -117
- oscura/loaders/wav.py +155 -68
- oscura/reporting/__init__.py +9 -7
- oscura/reporting/analyze.py +352 -146
- oscura/reporting/argument_preparer.py +69 -14
- oscura/reporting/auto_report.py +97 -61
- oscura/reporting/batch.py +131 -58
- oscura/reporting/chart_selection.py +57 -45
- oscura/reporting/comparison.py +63 -17
- oscura/reporting/content/executive.py +76 -24
- oscura/reporting/core_formats/multi_format.py +11 -8
- oscura/reporting/engine.py +312 -158
- oscura/reporting/enhanced_reports.py +949 -0
- oscura/reporting/export.py +86 -43
- oscura/reporting/formatting/numbers.py +69 -42
- oscura/reporting/html.py +139 -58
- oscura/reporting/index.py +137 -65
- oscura/reporting/output.py +158 -67
- oscura/reporting/pdf.py +67 -102
- oscura/reporting/plots.py +191 -112
- oscura/reporting/sections.py +88 -47
- oscura/reporting/standards.py +104 -61
- oscura/reporting/summary_generator.py +75 -55
- oscura/reporting/tables.py +138 -54
- oscura/reporting/templates/enhanced/protocol_re.html +525 -0
- oscura/reporting/templates/index.md +13 -13
- oscura/sessions/__init__.py +14 -23
- oscura/sessions/base.py +3 -3
- oscura/sessions/blackbox.py +106 -10
- oscura/sessions/generic.py +2 -2
- oscura/sessions/legacy.py +783 -0
- oscura/side_channel/__init__.py +63 -0
- oscura/side_channel/dpa.py +1025 -0
- oscura/utils/__init__.py +15 -1
- oscura/utils/autodetect.py +1 -5
- oscura/utils/bitwise.py +118 -0
- oscura/{builders → utils/builders}/__init__.py +1 -1
- oscura/{comparison → utils/comparison}/__init__.py +6 -6
- oscura/{comparison → utils/comparison}/compare.py +202 -101
- oscura/{comparison → utils/comparison}/golden.py +83 -63
- oscura/{comparison → utils/comparison}/limits.py +313 -89
- oscura/{comparison → utils/comparison}/mask.py +151 -45
- oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
- oscura/{comparison → utils/comparison}/visualization.py +147 -89
- oscura/{component → utils/component}/__init__.py +3 -3
- oscura/{component → utils/component}/impedance.py +122 -58
- oscura/{component → utils/component}/reactive.py +165 -168
- oscura/{component → utils/component}/transmission_line.py +3 -3
- oscura/{filtering → utils/filtering}/__init__.py +6 -6
- oscura/{filtering → utils/filtering}/base.py +1 -1
- oscura/{filtering → utils/filtering}/convenience.py +2 -2
- oscura/{filtering → utils/filtering}/design.py +169 -93
- oscura/{filtering → utils/filtering}/filters.py +2 -2
- oscura/{filtering → utils/filtering}/introspection.py +2 -2
- oscura/utils/geometry.py +31 -0
- oscura/utils/imports.py +184 -0
- oscura/utils/lazy.py +1 -1
- oscura/{math → utils/math}/__init__.py +2 -2
- oscura/{math → utils/math}/arithmetic.py +114 -48
- oscura/{math → utils/math}/interpolation.py +139 -106
- oscura/utils/memory.py +129 -66
- oscura/utils/memory_advanced.py +92 -9
- oscura/utils/memory_extensions.py +10 -8
- oscura/{optimization → utils/optimization}/__init__.py +1 -1
- oscura/{optimization → utils/optimization}/search.py +2 -2
- oscura/utils/performance/__init__.py +58 -0
- oscura/utils/performance/caching.py +889 -0
- oscura/utils/performance/lsh_clustering.py +333 -0
- oscura/utils/performance/memory_optimizer.py +699 -0
- oscura/utils/performance/optimizations.py +675 -0
- oscura/utils/performance/parallel.py +654 -0
- oscura/utils/performance/profiling.py +661 -0
- oscura/{pipeline → utils/pipeline}/base.py +1 -1
- oscura/{pipeline → utils/pipeline}/composition.py +11 -3
- oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
- oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
- oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
- oscura/{search → utils/search}/__init__.py +3 -3
- oscura/{search → utils/search}/anomaly.py +188 -58
- oscura/utils/search/context.py +294 -0
- oscura/{search → utils/search}/pattern.py +138 -10
- oscura/utils/serial.py +51 -0
- oscura/utils/storage/__init__.py +61 -0
- oscura/utils/storage/database.py +1166 -0
- oscura/{streaming → utils/streaming}/chunked.py +302 -143
- oscura/{streaming → utils/streaming}/progressive.py +1 -1
- oscura/{streaming → utils/streaming}/realtime.py +3 -2
- oscura/{triggering → utils/triggering}/__init__.py +6 -6
- oscura/{triggering → utils/triggering}/base.py +6 -6
- oscura/{triggering → utils/triggering}/edge.py +2 -2
- oscura/{triggering → utils/triggering}/pattern.py +2 -2
- oscura/{triggering → utils/triggering}/pulse.py +115 -74
- oscura/{triggering → utils/triggering}/window.py +2 -2
- oscura/utils/validation.py +32 -0
- oscura/validation/__init__.py +121 -0
- oscura/{compliance → validation/compliance}/__init__.py +5 -5
- oscura/{compliance → validation/compliance}/advanced.py +5 -5
- oscura/{compliance → validation/compliance}/masks.py +1 -1
- oscura/{compliance → validation/compliance}/reporting.py +127 -53
- oscura/{compliance → validation/compliance}/testing.py +114 -52
- oscura/validation/compliance_tests.py +915 -0
- oscura/validation/fuzzer.py +990 -0
- oscura/validation/grammar_tests.py +596 -0
- oscura/validation/grammar_validator.py +904 -0
- oscura/validation/hil_testing.py +977 -0
- oscura/{quality → validation/quality}/__init__.py +4 -4
- oscura/{quality → validation/quality}/ensemble.py +251 -171
- oscura/{quality → validation/quality}/explainer.py +3 -3
- oscura/{quality → validation/quality}/scoring.py +1 -1
- oscura/{quality → validation/quality}/warnings.py +4 -4
- oscura/validation/regression_suite.py +808 -0
- oscura/validation/replay.py +788 -0
- oscura/{testing → validation/testing}/__init__.py +2 -2
- oscura/{testing → validation/testing}/synthetic.py +5 -5
- oscura/visualization/__init__.py +9 -0
- oscura/visualization/accessibility.py +1 -1
- oscura/visualization/annotations.py +64 -67
- oscura/visualization/colors.py +7 -7
- oscura/visualization/digital.py +180 -81
- oscura/visualization/eye.py +236 -85
- oscura/visualization/interactive.py +320 -143
- oscura/visualization/jitter.py +587 -247
- oscura/visualization/layout.py +169 -134
- oscura/visualization/optimization.py +103 -52
- oscura/visualization/palettes.py +1 -1
- oscura/visualization/power.py +427 -211
- oscura/visualization/power_extended.py +626 -297
- oscura/visualization/presets.py +2 -0
- oscura/visualization/protocols.py +495 -181
- oscura/visualization/render.py +79 -63
- oscura/visualization/reverse_engineering.py +171 -124
- oscura/visualization/signal_integrity.py +460 -279
- oscura/visualization/specialized.py +190 -100
- oscura/visualization/spectral.py +670 -255
- oscura/visualization/thumbnails.py +166 -137
- oscura/visualization/waveform.py +150 -63
- oscura/workflows/__init__.py +3 -0
- oscura/{batch → workflows/batch}/__init__.py +5 -5
- oscura/{batch → workflows/batch}/advanced.py +150 -75
- oscura/workflows/batch/aggregate.py +531 -0
- oscura/workflows/batch/analyze.py +236 -0
- oscura/{batch → workflows/batch}/logging.py +2 -2
- oscura/{batch → workflows/batch}/metrics.py +1 -1
- oscura/workflows/complete_re.py +1144 -0
- oscura/workflows/compliance.py +44 -54
- oscura/workflows/digital.py +197 -51
- oscura/workflows/legacy/__init__.py +12 -0
- oscura/{workflow → workflows/legacy}/dag.py +4 -1
- oscura/workflows/multi_trace.py +9 -9
- oscura/workflows/power.py +42 -62
- oscura/workflows/protocol.py +82 -49
- oscura/workflows/reverse_engineering.py +351 -150
- oscura/workflows/signal_integrity.py +157 -82
- oscura-0.6.0.dist-info/METADATA +643 -0
- oscura-0.6.0.dist-info/RECORD +590 -0
- oscura/analyzers/digital/ic_database.py +0 -498
- oscura/analyzers/digital/timing_paths.py +0 -339
- oscura/analyzers/digital/vintage.py +0 -377
- oscura/analyzers/digital/vintage_result.py +0 -148
- oscura/analyzers/protocols/parallel_bus.py +0 -449
- oscura/batch/aggregate.py +0 -300
- oscura/batch/analyze.py +0 -139
- oscura/dsl/__init__.py +0 -73
- oscura/exceptions.py +0 -59
- oscura/exploratory/fuzzy.py +0 -513
- oscura/exploratory/sync.py +0 -384
- oscura/export/wavedrom.py +0 -430
- oscura/exporters/__init__.py +0 -94
- oscura/exporters/csv.py +0 -303
- oscura/exporters/exporters.py +0 -44
- oscura/exporters/hdf5.py +0 -217
- oscura/exporters/html_export.py +0 -701
- oscura/exporters/json_export.py +0 -338
- oscura/exporters/markdown_export.py +0 -367
- oscura/exporters/matlab_export.py +0 -354
- oscura/exporters/npz_export.py +0 -219
- oscura/exporters/spice_export.py +0 -210
- oscura/exporters/vintage_logic_csv.py +0 -247
- oscura/reporting/vintage_logic_report.py +0 -523
- oscura/search/context.py +0 -149
- oscura/session/__init__.py +0 -34
- oscura/session/annotations.py +0 -289
- oscura/session/history.py +0 -313
- oscura/session/session.py +0 -520
- oscura/visualization/digital_advanced.py +0 -718
- oscura/visualization/figure_manager.py +0 -156
- oscura/workflow/__init__.py +0 -13
- oscura-0.5.0.dist-info/METADATA +0 -407
- oscura-0.5.0.dist-info/RECORD +0 -486
- /oscura/core/{config.py → config/legacy.py} +0 -0
- /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
- /oscura/{extensibility → core/extensibility}/registry.py +0 -0
- /oscura/{plugins → core/plugins}/isolation.py +0 -0
- /oscura/{builders → utils/builders}/signal_builder.py +0 -0
- /oscura/{optimization → utils/optimization}/parallel.py +0 -0
- /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
- /oscura/{streaming → utils/streaming}/__init__.py +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -92,27 +92,61 @@ def plot_protocol_decode(
|
|
|
92
92
|
References:
|
|
93
93
|
VIS-030: Protocol Decode Visualization
|
|
94
94
|
"""
|
|
95
|
+
_validate_plot_inputs(packets)
|
|
96
|
+
protocol = packets[0].protocol
|
|
97
|
+
t_min, t_max, time_mult, time_unit = _calculate_time_parameters(packets, time_range, time_unit)
|
|
98
|
+
fig, axes = _create_figure_layout(trace, figsize)
|
|
99
|
+
ax_idx = _plot_waveform_if_present(
|
|
100
|
+
axes, trace, trace_channel, protocol, t_min, t_max, time_mult
|
|
101
|
+
)
|
|
102
|
+
_plot_packet_timeline(
|
|
103
|
+
axes[ax_idx], packets, protocol, t_min, t_max, time_mult, show_data, show_errors, colorize
|
|
104
|
+
)
|
|
105
|
+
_finalize_plot_layout(axes, t_min, t_max, time_mult, time_unit, title)
|
|
106
|
+
|
|
107
|
+
return fig
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _validate_plot_inputs(packets: list[ProtocolPacket]) -> None:
|
|
111
|
+
"""Validate plot inputs.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
packets: List of protocol packets.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ImportError: If matplotlib not available.
|
|
118
|
+
ValueError: If packets list is empty.
|
|
119
|
+
"""
|
|
95
120
|
if not HAS_MATPLOTLIB:
|
|
96
121
|
raise ImportError("matplotlib is required for visualization")
|
|
97
|
-
|
|
98
122
|
if len(packets) == 0:
|
|
99
123
|
raise ValueError("packets list cannot be empty")
|
|
100
124
|
|
|
101
|
-
# Determine protocol name from first packet
|
|
102
|
-
protocol = packets[0].protocol
|
|
103
125
|
|
|
104
|
-
|
|
126
|
+
def _calculate_time_parameters(
|
|
127
|
+
packets: list[ProtocolPacket],
|
|
128
|
+
time_range: tuple[float, float] | None,
|
|
129
|
+
time_unit: str,
|
|
130
|
+
) -> tuple[float, float, float, str]:
|
|
131
|
+
"""Calculate time range and multiplier for plotting.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
packets: List of packets for auto time range.
|
|
135
|
+
time_range: User-specified time range or None.
|
|
136
|
+
time_unit: Time unit string.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Tuple of (t_min, t_max, time_mult, time_unit).
|
|
140
|
+
"""
|
|
105
141
|
if time_range is None:
|
|
106
142
|
t_min = min(p.timestamp for p in packets)
|
|
107
143
|
t_max = max(p.end_timestamp if p.end_timestamp else p.timestamp for p in packets)
|
|
108
|
-
# Add 10% padding
|
|
109
144
|
padding = (t_max - t_min) * 0.1
|
|
110
145
|
t_min -= padding
|
|
111
146
|
t_max += padding
|
|
112
147
|
else:
|
|
113
148
|
t_min, t_max = time_range
|
|
114
149
|
|
|
115
|
-
# Select time unit
|
|
116
150
|
if time_unit == "auto":
|
|
117
151
|
time_range_val = t_max - t_min
|
|
118
152
|
if time_range_val < 1e-6:
|
|
@@ -130,10 +164,23 @@ def plot_protocol_decode(
|
|
|
130
164
|
else:
|
|
131
165
|
time_mult = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
132
166
|
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
return t_min, t_max, time_mult, time_unit
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _create_figure_layout(
|
|
171
|
+
trace: DigitalTrace | None, figsize: tuple[float, float] | None
|
|
172
|
+
) -> tuple[Figure, list[Axes]]:
|
|
173
|
+
"""Create figure and axes layout.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
trace: Optional trace for determining row count.
|
|
177
|
+
figsize: Figure size or None for auto.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Tuple of (figure, axes_list).
|
|
181
|
+
"""
|
|
182
|
+
n_rows = 1 if trace is None else 2
|
|
135
183
|
|
|
136
|
-
# Auto-calculate figure size
|
|
137
184
|
if figsize is None:
|
|
138
185
|
width = 14
|
|
139
186
|
height = max(4, n_rows * 1.5 + 1)
|
|
@@ -150,35 +197,78 @@ def plot_protocol_decode(
|
|
|
150
197
|
if n_rows == 1:
|
|
151
198
|
axes = [axes]
|
|
152
199
|
|
|
153
|
-
|
|
200
|
+
return fig, axes
|
|
154
201
|
|
|
155
|
-
# Plot waveform if provided
|
|
156
|
-
if trace is not None:
|
|
157
|
-
ax = axes[ax_idx]
|
|
158
|
-
ax_idx += 1
|
|
159
202
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
203
|
+
def _plot_waveform_if_present(
|
|
204
|
+
axes: list[Axes],
|
|
205
|
+
trace: DigitalTrace | None,
|
|
206
|
+
trace_channel: str | None,
|
|
207
|
+
protocol: str,
|
|
208
|
+
t_min: float,
|
|
209
|
+
t_max: float,
|
|
210
|
+
time_mult: float,
|
|
211
|
+
) -> int:
|
|
212
|
+
"""Plot waveform trace if provided.
|
|
163
213
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
214
|
+
Args:
|
|
215
|
+
axes: List of axes to plot on.
|
|
216
|
+
trace: Optional digital trace.
|
|
217
|
+
trace_channel: Channel name override.
|
|
218
|
+
protocol: Protocol name for default label.
|
|
219
|
+
t_min: Minimum time value.
|
|
220
|
+
t_max: Maximum time value.
|
|
221
|
+
time_mult: Time unit multiplier.
|
|
168
222
|
|
|
169
|
-
|
|
170
|
-
|
|
223
|
+
Returns:
|
|
224
|
+
Index of next available axis.
|
|
225
|
+
"""
|
|
226
|
+
if trace is None:
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
ax = axes[0]
|
|
230
|
+
trace_time = trace.time_vector * time_mult
|
|
231
|
+
trace_data = trace.data.astype(float)
|
|
232
|
+
|
|
233
|
+
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
234
|
+
trace_time = trace_time[mask]
|
|
235
|
+
trace_data = trace_data[mask]
|
|
236
|
+
|
|
237
|
+
_plot_digital_waveform(ax, trace_time, trace_data)
|
|
238
|
+
|
|
239
|
+
channel_name = trace_channel if trace_channel else protocol
|
|
240
|
+
ax.set_ylabel(channel_name, rotation=0, ha="right", va="center", fontsize=10)
|
|
241
|
+
ax.set_ylim(-0.2, 1.3)
|
|
242
|
+
ax.set_yticks([])
|
|
243
|
+
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
244
|
+
|
|
245
|
+
return 1
|
|
171
246
|
|
|
172
|
-
channel_name = trace_channel if trace_channel else protocol
|
|
173
|
-
ax.set_ylabel(channel_name, rotation=0, ha="right", va="center", fontsize=10)
|
|
174
|
-
ax.set_ylim(-0.2, 1.3)
|
|
175
|
-
ax.set_yticks([])
|
|
176
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
177
247
|
|
|
178
|
-
|
|
179
|
-
ax
|
|
248
|
+
def _plot_packet_timeline(
|
|
249
|
+
ax: Axes,
|
|
250
|
+
packets: list[ProtocolPacket],
|
|
251
|
+
protocol: str,
|
|
252
|
+
t_min: float,
|
|
253
|
+
t_max: float,
|
|
254
|
+
time_mult: float,
|
|
255
|
+
show_data: bool,
|
|
256
|
+
show_errors: bool,
|
|
257
|
+
colorize: bool,
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Plot packet timeline on axis.
|
|
180
260
|
|
|
181
|
-
|
|
261
|
+
Args:
|
|
262
|
+
ax: Matplotlib axis.
|
|
263
|
+
packets: List of packets to plot.
|
|
264
|
+
protocol: Protocol name.
|
|
265
|
+
t_min: Minimum time value.
|
|
266
|
+
t_max: Maximum time value.
|
|
267
|
+
time_mult: Time unit multiplier.
|
|
268
|
+
show_data: Show data annotations.
|
|
269
|
+
show_errors: Highlight errors.
|
|
270
|
+
colorize: Use color coding.
|
|
271
|
+
"""
|
|
182
272
|
for packet in packets:
|
|
183
273
|
if packet.timestamp < t_min or packet.timestamp > t_max:
|
|
184
274
|
continue
|
|
@@ -188,15 +278,8 @@ def plot_protocol_decode(
|
|
|
188
278
|
packet.end_timestamp if packet.end_timestamp else packet.timestamp + 0.001
|
|
189
279
|
) * time_mult
|
|
190
280
|
|
|
191
|
-
|
|
192
|
-
if show_errors and packet.errors:
|
|
193
|
-
color = "#ff6b6b" # Red for errors
|
|
194
|
-
elif colorize:
|
|
195
|
-
color = _get_packet_color(packet, protocol)
|
|
196
|
-
else:
|
|
197
|
-
color = "#4ecdc4" # Default teal
|
|
281
|
+
color = _determine_packet_color(packet, protocol, show_errors, colorize)
|
|
198
282
|
|
|
199
|
-
# Draw packet rectangle
|
|
200
283
|
rect = patches.Rectangle(
|
|
201
284
|
(start, 0.1),
|
|
202
285
|
end - start,
|
|
@@ -208,45 +291,93 @@ def plot_protocol_decode(
|
|
|
208
291
|
)
|
|
209
292
|
ax.add_patch(rect)
|
|
210
293
|
|
|
211
|
-
# Add data annotation
|
|
212
294
|
if show_data and packet.data:
|
|
213
|
-
|
|
214
|
-
mid_time = (start + end) / 2
|
|
215
|
-
ax.text(
|
|
216
|
-
mid_time,
|
|
217
|
-
0.5,
|
|
218
|
-
data_str,
|
|
219
|
-
ha="center",
|
|
220
|
-
va="center",
|
|
221
|
-
fontsize=8,
|
|
222
|
-
fontweight="bold",
|
|
223
|
-
color="white" if not (show_errors and packet.errors) else "black",
|
|
224
|
-
)
|
|
295
|
+
_add_packet_annotation(ax, packet, start, end, show_errors)
|
|
225
296
|
|
|
226
|
-
# Add error markers
|
|
227
297
|
if show_errors and packet.errors:
|
|
228
|
-
ax.plot(
|
|
229
|
-
start,
|
|
230
|
-
1.1,
|
|
231
|
-
"rx",
|
|
232
|
-
markersize=8,
|
|
233
|
-
markeredgewidth=2,
|
|
234
|
-
)
|
|
298
|
+
ax.plot(start, 1.1, "rx", markersize=8, markeredgewidth=2)
|
|
235
299
|
|
|
236
300
|
ax.set_ylabel(f"{protocol}\nPackets", rotation=0, ha="right", va="center", fontsize=10)
|
|
237
301
|
ax.set_ylim(0, 1.2)
|
|
238
302
|
ax.set_yticks([])
|
|
239
303
|
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
240
304
|
|
|
241
|
-
|
|
305
|
+
|
|
306
|
+
def _determine_packet_color(
|
|
307
|
+
packet: ProtocolPacket, protocol: str, show_errors: bool, colorize: bool
|
|
308
|
+
) -> str:
|
|
309
|
+
"""Determine packet rectangle color.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
packet: Protocol packet.
|
|
313
|
+
protocol: Protocol name.
|
|
314
|
+
show_errors: Whether to highlight errors.
|
|
315
|
+
colorize: Whether to use protocol colors.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
Color string.
|
|
319
|
+
"""
|
|
320
|
+
if show_errors and packet.errors:
|
|
321
|
+
return "#ff6b6b"
|
|
322
|
+
elif colorize:
|
|
323
|
+
return _get_packet_color(packet, protocol)
|
|
324
|
+
else:
|
|
325
|
+
return "#4ecdc4"
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _add_packet_annotation(
|
|
329
|
+
ax: Axes, packet: ProtocolPacket, start: float, end: float, show_errors: bool
|
|
330
|
+
) -> None:
|
|
331
|
+
"""Add data annotation to packet.
|
|
332
|
+
|
|
333
|
+
Args:
|
|
334
|
+
ax: Matplotlib axis.
|
|
335
|
+
packet: Protocol packet.
|
|
336
|
+
start: Start time in scaled units.
|
|
337
|
+
end: End time in scaled units.
|
|
338
|
+
show_errors: Whether errors are highlighted.
|
|
339
|
+
"""
|
|
340
|
+
data_str = _format_packet_data(packet)
|
|
341
|
+
mid_time = (start + end) / 2
|
|
342
|
+
text_color = "white" if not (show_errors and packet.errors) else "black"
|
|
343
|
+
ax.text(
|
|
344
|
+
mid_time,
|
|
345
|
+
0.5,
|
|
346
|
+
data_str,
|
|
347
|
+
ha="center",
|
|
348
|
+
va="center",
|
|
349
|
+
fontsize=8,
|
|
350
|
+
fontweight="bold",
|
|
351
|
+
color=text_color,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _finalize_plot_layout(
|
|
356
|
+
axes: list[Axes],
|
|
357
|
+
t_min: float,
|
|
358
|
+
t_max: float,
|
|
359
|
+
time_mult: float,
|
|
360
|
+
time_unit: str,
|
|
361
|
+
title: str | None,
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Finalize plot layout with labels and title.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
axes: List of axes.
|
|
367
|
+
t_min: Minimum time value.
|
|
368
|
+
t_max: Maximum time value.
|
|
369
|
+
time_mult: Time unit multiplier.
|
|
370
|
+
time_unit: Time unit string.
|
|
371
|
+
title: Optional plot title.
|
|
372
|
+
"""
|
|
242
373
|
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
243
374
|
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
244
375
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
376
|
+
fig = axes[0].get_figure()
|
|
377
|
+
if fig and hasattr(fig, "tight_layout"):
|
|
378
|
+
if title:
|
|
379
|
+
fig.suptitle(title, fontsize=14, y=0.98)
|
|
380
|
+
fig.tight_layout()
|
|
250
381
|
|
|
251
382
|
|
|
252
383
|
def plot_uart_decode(
|
|
@@ -350,38 +481,43 @@ def _plot_dual_channel_uart(
|
|
|
350
481
|
Returns:
|
|
351
482
|
Matplotlib Figure object.
|
|
352
483
|
"""
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
t_min = min(p.timestamp for p in packets)
|
|
356
|
-
t_max = max(p.end_timestamp if p.end_timestamp else p.timestamp for p in packets)
|
|
357
|
-
padding = (t_max - t_min) * 0.1
|
|
358
|
-
t_min -= padding
|
|
359
|
-
t_max += padding
|
|
360
|
-
else:
|
|
361
|
-
t_min, t_max = time_range
|
|
484
|
+
# Calculate time parameters
|
|
485
|
+
t_min, t_max, time_mult, time_unit = _determine_time_params(packets, time_range, time_unit)
|
|
362
486
|
|
|
363
|
-
#
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
487
|
+
# Create figure with 4 rows
|
|
488
|
+
fig, axes = _create_dual_uart_figure(figsize)
|
|
489
|
+
|
|
490
|
+
# Separate packets by channel
|
|
491
|
+
rx_packets, tx_packets = _separate_uart_packets(packets)
|
|
492
|
+
show_errors = show_parity_errors or show_framing_errors
|
|
493
|
+
|
|
494
|
+
# Plot all four rows
|
|
495
|
+
_plot_uart_channel_pair(
|
|
496
|
+
axes[0], axes[1], rx_trace, rx_packets, "RX", t_min, t_max, time_mult, show_errors
|
|
497
|
+
)
|
|
498
|
+
_plot_uart_channel_pair(
|
|
499
|
+
axes[2], axes[3], tx_trace, tx_packets, "TX", t_min, t_max, time_mult, show_errors
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Finalize plot
|
|
503
|
+
_finalize_uart_plot(fig, axes, t_min, t_max, time_mult, time_unit, title)
|
|
504
|
+
|
|
505
|
+
return fig
|
|
380
506
|
|
|
381
|
-
|
|
507
|
+
|
|
508
|
+
def _create_dual_uart_figure(
|
|
509
|
+
figsize: tuple[float, float] | None,
|
|
510
|
+
) -> tuple[Figure, list[Axes]]:
|
|
511
|
+
"""Create figure for dual-channel UART plot.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
figsize: Figure size or None for auto-calculation.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Tuple of (figure, axes_list).
|
|
518
|
+
"""
|
|
382
519
|
n_rows = 4
|
|
383
520
|
|
|
384
|
-
# Auto-calculate figure size
|
|
385
521
|
if figsize is None:
|
|
386
522
|
width = 14
|
|
387
523
|
height = max(6, n_rows * 1.2 + 1)
|
|
@@ -395,11 +531,24 @@ def _plot_dual_channel_uart(
|
|
|
395
531
|
gridspec_kw={"hspace": 0.1, "height_ratios": [1, 0.8, 1, 0.8]},
|
|
396
532
|
)
|
|
397
533
|
|
|
398
|
-
|
|
534
|
+
return fig, axes
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _separate_uart_packets(
|
|
538
|
+
packets: list[ProtocolPacket],
|
|
539
|
+
) -> tuple[list[ProtocolPacket], list[ProtocolPacket]]:
|
|
540
|
+
"""Separate UART packets by channel (RX vs TX).
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
packets: List of UART packets.
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
Tuple of (rx_packets, tx_packets).
|
|
547
|
+
"""
|
|
399
548
|
rx_packets = []
|
|
400
549
|
tx_packets = []
|
|
550
|
+
|
|
401
551
|
for packet in packets:
|
|
402
|
-
# Check packet metadata for channel info
|
|
403
552
|
channel = getattr(packet, "channel", None)
|
|
404
553
|
if channel is None and hasattr(packet, "metadata"):
|
|
405
554
|
channel = packet.metadata.get("channel") if isinstance(packet.metadata, dict) else None
|
|
@@ -407,49 +556,74 @@ def _plot_dual_channel_uart(
|
|
|
407
556
|
if channel == "TX":
|
|
408
557
|
tx_packets.append(packet)
|
|
409
558
|
else:
|
|
410
|
-
# Default to RX if channel not specified
|
|
411
559
|
rx_packets.append(packet)
|
|
412
560
|
|
|
413
|
-
# If no channel info, put all packets on
|
|
561
|
+
# If no channel info, put all packets on RX
|
|
414
562
|
if not rx_packets and not tx_packets:
|
|
415
563
|
rx_packets = packets
|
|
416
|
-
tx_packets = []
|
|
417
564
|
|
|
418
|
-
|
|
565
|
+
return rx_packets, tx_packets
|
|
419
566
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
567
|
+
|
|
568
|
+
def _plot_uart_channel_pair(
|
|
569
|
+
ax_wave: Axes,
|
|
570
|
+
ax_packets: Axes,
|
|
571
|
+
trace: DigitalTrace,
|
|
572
|
+
packets: list[ProtocolPacket],
|
|
573
|
+
label: str,
|
|
574
|
+
t_min: float,
|
|
575
|
+
t_max: float,
|
|
576
|
+
time_mult: float,
|
|
577
|
+
show_errors: bool,
|
|
578
|
+
) -> None:
|
|
579
|
+
"""Plot waveform and packet row for a single UART channel.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
ax_wave: Axis for waveform plot.
|
|
583
|
+
ax_packets: Axis for packet annotations.
|
|
584
|
+
trace: Digital trace for the channel.
|
|
585
|
+
packets: Packets for this channel.
|
|
586
|
+
label: Channel label (e.g., "RX" or "TX").
|
|
587
|
+
t_min: Minimum time value.
|
|
588
|
+
t_max: Maximum time value.
|
|
589
|
+
time_mult: Time unit multiplier.
|
|
590
|
+
show_errors: Whether to highlight errors.
|
|
591
|
+
"""
|
|
592
|
+
# Plot waveform
|
|
593
|
+
trace_time = trace.time_vector * time_mult
|
|
594
|
+
trace_data = trace.data.astype(float)
|
|
595
|
+
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
596
|
+
_plot_digital_waveform(ax_wave, trace_time[mask], trace_data[mask])
|
|
597
|
+
ax_wave.set_ylabel(label, rotation=0, ha="right", va="center", fontsize=10)
|
|
598
|
+
ax_wave.set_ylim(-0.2, 1.3)
|
|
599
|
+
ax_wave.set_yticks([])
|
|
600
|
+
ax_wave.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
601
|
+
|
|
602
|
+
# Plot packets
|
|
603
|
+
_plot_packet_row(ax_packets, packets, t_min, t_max, time_mult, show_errors)
|
|
604
|
+
ax_packets.set_ylabel(f"{label}\nData", rotation=0, ha="right", va="center", fontsize=9)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _finalize_uart_plot(
|
|
608
|
+
fig: Figure,
|
|
609
|
+
axes: list[Axes],
|
|
610
|
+
t_min: float,
|
|
611
|
+
t_max: float,
|
|
612
|
+
time_mult: float,
|
|
613
|
+
time_unit: str,
|
|
614
|
+
title: str | None,
|
|
615
|
+
) -> None:
|
|
616
|
+
"""Add final formatting to UART plot.
|
|
617
|
+
|
|
618
|
+
Args:
|
|
619
|
+
fig: Matplotlib figure.
|
|
620
|
+
axes: List of axes.
|
|
621
|
+
t_min: Minimum time value.
|
|
622
|
+
t_max: Maximum time value.
|
|
623
|
+
time_mult: Time multiplier.
|
|
624
|
+
time_unit: Time unit string.
|
|
625
|
+
title: Plot title or None.
|
|
626
|
+
"""
|
|
453
627
|
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
454
628
|
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
455
629
|
|
|
@@ -457,7 +631,6 @@ def _plot_dual_channel_uart(
|
|
|
457
631
|
fig.suptitle(title, fontsize=14, y=0.98)
|
|
458
632
|
|
|
459
633
|
fig.tight_layout()
|
|
460
|
-
return fig
|
|
461
634
|
|
|
462
635
|
|
|
463
636
|
def _plot_packet_row(
|
|
@@ -634,7 +807,37 @@ def _plot_multi_channel_spi(
|
|
|
634
807
|
Returns:
|
|
635
808
|
Matplotlib Figure object.
|
|
636
809
|
"""
|
|
637
|
-
|
|
810
|
+
t_min, t_max, time_mult, time_unit = _determine_time_params(packets, time_range, time_unit)
|
|
811
|
+
rows = _build_spi_row_list(cs_trace, clk_trace, mosi_trace, miso_trace, show_mosi, show_miso)
|
|
812
|
+
|
|
813
|
+
if len(rows) == 0:
|
|
814
|
+
return plot_protocol_decode(
|
|
815
|
+
packets, time_range=(t_min, t_max), time_unit=time_unit, figsize=figsize, title=title
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
fig, axes = _create_spi_figure(rows, figsize)
|
|
819
|
+
mosi_packets, miso_packets = _separate_spi_packets(packets)
|
|
820
|
+
_render_spi_rows(axes, rows, t_min, t_max, time_mult, mosi_packets, miso_packets)
|
|
821
|
+
_finalize_spi_plot(axes, t_min, t_max, time_mult, time_unit, title)
|
|
822
|
+
|
|
823
|
+
return fig
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def _determine_time_params(
|
|
827
|
+
packets: list[ProtocolPacket],
|
|
828
|
+
time_range: tuple[float, float] | None,
|
|
829
|
+
time_unit: str,
|
|
830
|
+
) -> tuple[float, float, float, str]:
|
|
831
|
+
"""Determine time range and multiplier for SPI plot.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
packets: List of SPI packets for time range calculation.
|
|
835
|
+
time_range: User-specified time range or None for auto.
|
|
836
|
+
time_unit: Time unit ("auto" or specific unit).
|
|
837
|
+
|
|
838
|
+
Returns:
|
|
839
|
+
Tuple of (t_min, t_max, time_mult, time_unit).
|
|
840
|
+
"""
|
|
638
841
|
if time_range is None:
|
|
639
842
|
t_min = min(p.timestamp for p in packets)
|
|
640
843
|
t_max = max(p.end_timestamp if p.end_timestamp else p.timestamp for p in packets)
|
|
@@ -644,7 +847,6 @@ def _plot_multi_channel_spi(
|
|
|
644
847
|
else:
|
|
645
848
|
t_min, t_max = time_range
|
|
646
849
|
|
|
647
|
-
# Select time unit
|
|
648
850
|
if time_unit == "auto":
|
|
649
851
|
time_range_val = t_max - t_min
|
|
650
852
|
if time_range_val < 1e-6:
|
|
@@ -662,45 +864,64 @@ def _plot_multi_channel_spi(
|
|
|
662
864
|
else:
|
|
663
865
|
time_mult = {"s": 1.0, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
664
866
|
|
|
665
|
-
|
|
867
|
+
return t_min, t_max, time_mult, time_unit
|
|
868
|
+
|
|
869
|
+
|
|
870
|
+
def _build_spi_row_list(
|
|
871
|
+
cs_trace: DigitalTrace | None,
|
|
872
|
+
clk_trace: DigitalTrace | None,
|
|
873
|
+
mosi_trace: DigitalTrace | None,
|
|
874
|
+
miso_trace: DigitalTrace | None,
|
|
875
|
+
show_mosi: bool,
|
|
876
|
+
show_miso: bool,
|
|
877
|
+
) -> list[dict[str, Any]]:
|
|
878
|
+
"""Build list of row specifications for SPI multi-channel plot.
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
cs_trace: Chip select trace.
|
|
882
|
+
clk_trace: Clock trace.
|
|
883
|
+
mosi_trace: MOSI trace.
|
|
884
|
+
miso_trace: MISO trace.
|
|
885
|
+
show_mosi: Whether to show MOSI data row.
|
|
886
|
+
show_miso: Whether to show MISO data row.
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
List of row dictionaries specifying type, trace, label, channel.
|
|
890
|
+
"""
|
|
666
891
|
rows: list[dict[str, Any]] = []
|
|
667
892
|
|
|
668
893
|
if cs_trace is not None:
|
|
669
894
|
rows.append({"type": "waveform", "trace": cs_trace, "label": "CS"})
|
|
670
|
-
|
|
671
895
|
if clk_trace is not None:
|
|
672
896
|
rows.append({"type": "waveform", "trace": clk_trace, "label": "CLK"})
|
|
673
|
-
|
|
674
897
|
if mosi_trace is not None:
|
|
675
898
|
rows.append({"type": "waveform", "trace": mosi_trace, "label": "MOSI"})
|
|
676
899
|
if show_mosi:
|
|
677
900
|
rows.append({"type": "packets", "label": "MOSI\nData", "channel": "MOSI"})
|
|
678
|
-
|
|
679
901
|
if miso_trace is not None:
|
|
680
902
|
rows.append({"type": "waveform", "trace": miso_trace, "label": "MISO"})
|
|
681
903
|
if show_miso:
|
|
682
904
|
rows.append({"type": "packets", "label": "MISO\nData", "channel": "MISO"})
|
|
683
905
|
|
|
684
|
-
|
|
685
|
-
if n_rows == 0:
|
|
686
|
-
# Fallback to generic if no traces
|
|
687
|
-
return plot_protocol_decode(
|
|
688
|
-
packets,
|
|
689
|
-
time_range=time_range,
|
|
690
|
-
time_unit=time_unit,
|
|
691
|
-
figsize=figsize,
|
|
692
|
-
title=title,
|
|
693
|
-
)
|
|
906
|
+
return rows
|
|
694
907
|
|
|
695
|
-
# Calculate height ratios (waveforms get more space than data rows)
|
|
696
|
-
height_ratios = []
|
|
697
|
-
for row in rows:
|
|
698
|
-
if row["type"] == "waveform":
|
|
699
|
-
height_ratios.append(1.0)
|
|
700
|
-
else:
|
|
701
|
-
height_ratios.append(0.6)
|
|
702
908
|
|
|
703
|
-
|
|
909
|
+
def _create_spi_figure(
|
|
910
|
+
rows: list[dict[str, Any]],
|
|
911
|
+
figsize: tuple[float, float] | None,
|
|
912
|
+
) -> tuple[Figure, list[Axes]]:
|
|
913
|
+
"""Create matplotlib figure and axes for SPI plot.
|
|
914
|
+
|
|
915
|
+
Args:
|
|
916
|
+
rows: Row specifications from _build_spi_row_list.
|
|
917
|
+
figsize: Figure size or None for auto-calculation.
|
|
918
|
+
|
|
919
|
+
Returns:
|
|
920
|
+
Tuple of (figure, axes_list).
|
|
921
|
+
"""
|
|
922
|
+
height_ratios = [1.0 if row["type"] == "waveform" else 0.6 for row in rows]
|
|
923
|
+
n_rows = len(rows)
|
|
924
|
+
|
|
704
925
|
if figsize is None:
|
|
705
926
|
width = 14
|
|
706
927
|
height = max(4, sum(height_ratios) * 1.2 + 1)
|
|
@@ -713,13 +934,26 @@ def _plot_multi_channel_spi(
|
|
|
713
934
|
sharex=True,
|
|
714
935
|
gridspec_kw={"hspace": 0.1, "height_ratios": height_ratios},
|
|
715
936
|
)
|
|
716
|
-
|
|
717
937
|
if n_rows == 1:
|
|
718
938
|
axes = [axes]
|
|
719
939
|
|
|
720
|
-
|
|
940
|
+
return fig, axes
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
def _separate_spi_packets(
|
|
944
|
+
packets: list[ProtocolPacket],
|
|
945
|
+
) -> tuple[list[ProtocolPacket], list[ProtocolPacket]]:
|
|
946
|
+
"""Separate packets by channel (MOSI vs MISO).
|
|
947
|
+
|
|
948
|
+
Args:
|
|
949
|
+
packets: List of SPI packets.
|
|
950
|
+
|
|
951
|
+
Returns:
|
|
952
|
+
Tuple of (mosi_packets, miso_packets).
|
|
953
|
+
"""
|
|
721
954
|
mosi_packets = []
|
|
722
955
|
miso_packets = []
|
|
956
|
+
|
|
723
957
|
for packet in packets:
|
|
724
958
|
channel = getattr(packet, "channel", None)
|
|
725
959
|
if channel is None and hasattr(packet, "metadata"):
|
|
@@ -728,41 +962,121 @@ def _plot_multi_channel_spi(
|
|
|
728
962
|
if channel == "MISO":
|
|
729
963
|
miso_packets.append(packet)
|
|
730
964
|
else:
|
|
731
|
-
# Default to MOSI
|
|
732
965
|
mosi_packets.append(packet)
|
|
733
966
|
|
|
734
|
-
# If no channel info, use all packets for MOSI
|
|
735
967
|
if not mosi_packets and not miso_packets:
|
|
736
968
|
mosi_packets = packets
|
|
737
969
|
|
|
738
|
-
|
|
970
|
+
return mosi_packets, miso_packets
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def _render_spi_rows(
|
|
974
|
+
axes: list[Axes],
|
|
975
|
+
rows: list[dict[str, Any]],
|
|
976
|
+
t_min: float,
|
|
977
|
+
t_max: float,
|
|
978
|
+
time_mult: float,
|
|
979
|
+
mosi_packets: list[ProtocolPacket],
|
|
980
|
+
miso_packets: list[ProtocolPacket],
|
|
981
|
+
) -> None:
|
|
982
|
+
"""Render all SPI plot rows (waveforms and packet data).
|
|
983
|
+
|
|
984
|
+
Args:
|
|
985
|
+
axes: List of matplotlib axes.
|
|
986
|
+
rows: Row specifications.
|
|
987
|
+
t_min: Minimum time value.
|
|
988
|
+
t_max: Maximum time value.
|
|
989
|
+
time_mult: Time multiplier for unit conversion.
|
|
990
|
+
mosi_packets: MOSI channel packets.
|
|
991
|
+
miso_packets: MISO channel packets.
|
|
992
|
+
"""
|
|
739
993
|
for ax, row in zip(axes, rows, strict=False):
|
|
740
994
|
if row["type"] == "waveform":
|
|
741
|
-
|
|
742
|
-
trace_time = trace.time_vector * time_mult
|
|
743
|
-
trace_data = trace.data.astype(float)
|
|
744
|
-
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
745
|
-
_plot_digital_waveform(ax, trace_time[mask], trace_data[mask])
|
|
746
|
-
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=10)
|
|
747
|
-
ax.set_ylim(-0.2, 1.3)
|
|
748
|
-
ax.set_yticks([])
|
|
749
|
-
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
995
|
+
_render_spi_waveform_row(ax, row, t_min, t_max, time_mult)
|
|
750
996
|
else:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
pkts = mosi_packets if channel == "MOSI" else miso_packets
|
|
754
|
-
_plot_packet_row(ax, pkts, t_min, t_max, time_mult, show_errors=True)
|
|
755
|
-
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=9)
|
|
997
|
+
_render_spi_packet_row(ax, row, t_min, t_max, time_mult, mosi_packets, miso_packets)
|
|
998
|
+
|
|
756
999
|
|
|
757
|
-
|
|
1000
|
+
def _render_spi_waveform_row(
|
|
1001
|
+
ax: Axes,
|
|
1002
|
+
row: dict[str, Any],
|
|
1003
|
+
t_min: float,
|
|
1004
|
+
t_max: float,
|
|
1005
|
+
time_mult: float,
|
|
1006
|
+
) -> None:
|
|
1007
|
+
"""Render a single waveform row for SPI plot.
|
|
1008
|
+
|
|
1009
|
+
Args:
|
|
1010
|
+
ax: Matplotlib axis to render on.
|
|
1011
|
+
row: Row specification with trace and label.
|
|
1012
|
+
t_min: Minimum time value.
|
|
1013
|
+
t_max: Maximum time value.
|
|
1014
|
+
time_mult: Time multiplier for unit conversion.
|
|
1015
|
+
"""
|
|
1016
|
+
trace = row["trace"]
|
|
1017
|
+
trace_time = trace.time_vector * time_mult
|
|
1018
|
+
trace_data = trace.data.astype(float)
|
|
1019
|
+
mask = (trace_time >= t_min * time_mult) & (trace_time <= t_max * time_mult)
|
|
1020
|
+
_plot_digital_waveform(ax, trace_time[mask], trace_data[mask])
|
|
1021
|
+
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=10)
|
|
1022
|
+
ax.set_ylim(-0.2, 1.3)
|
|
1023
|
+
ax.set_yticks([])
|
|
1024
|
+
ax.grid(True, axis="x", alpha=0.3, linestyle=":")
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def _render_spi_packet_row(
|
|
1028
|
+
ax: Axes,
|
|
1029
|
+
row: dict[str, Any],
|
|
1030
|
+
t_min: float,
|
|
1031
|
+
t_max: float,
|
|
1032
|
+
time_mult: float,
|
|
1033
|
+
mosi_packets: list[ProtocolPacket],
|
|
1034
|
+
miso_packets: list[ProtocolPacket],
|
|
1035
|
+
) -> None:
|
|
1036
|
+
"""Render a single packet data row for SPI plot.
|
|
1037
|
+
|
|
1038
|
+
Args:
|
|
1039
|
+
ax: Matplotlib axis to render on.
|
|
1040
|
+
row: Row specification with channel and label.
|
|
1041
|
+
t_min: Minimum time value.
|
|
1042
|
+
t_max: Maximum time value.
|
|
1043
|
+
time_mult: Time multiplier for unit conversion.
|
|
1044
|
+
mosi_packets: MOSI channel packets.
|
|
1045
|
+
miso_packets: MISO channel packets.
|
|
1046
|
+
"""
|
|
1047
|
+
channel = row.get("channel", "MOSI")
|
|
1048
|
+
pkts = mosi_packets if channel == "MOSI" else miso_packets
|
|
1049
|
+
_plot_packet_row(ax, pkts, t_min, t_max, time_mult, show_errors=True)
|
|
1050
|
+
ax.set_ylabel(row["label"], rotation=0, ha="right", va="center", fontsize=9)
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
def _finalize_spi_plot(
|
|
1054
|
+
axes: list[Axes],
|
|
1055
|
+
t_min: float,
|
|
1056
|
+
t_max: float,
|
|
1057
|
+
time_mult: float,
|
|
1058
|
+
time_unit: str,
|
|
1059
|
+
title: str | None,
|
|
1060
|
+
) -> None:
|
|
1061
|
+
"""Add final formatting to SPI plot.
|
|
1062
|
+
|
|
1063
|
+
Args:
|
|
1064
|
+
axes: List of matplotlib axes.
|
|
1065
|
+
t_min: Minimum time value.
|
|
1066
|
+
t_max: Maximum time value.
|
|
1067
|
+
time_mult: Time multiplier for unit conversion.
|
|
1068
|
+
time_unit: Time unit string for label.
|
|
1069
|
+
title: Plot title or None.
|
|
1070
|
+
"""
|
|
758
1071
|
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
759
1072
|
axes[-1].set_xlim(t_min * time_mult, t_max * time_mult)
|
|
760
1073
|
|
|
761
|
-
|
|
1074
|
+
fig = axes[0].get_figure()
|
|
1075
|
+
if title and fig is not None:
|
|
762
1076
|
fig.suptitle(title, fontsize=14, y=0.98)
|
|
763
1077
|
|
|
764
|
-
fig
|
|
765
|
-
|
|
1078
|
+
if fig is not None and hasattr(fig, "tight_layout"):
|
|
1079
|
+
fig.tight_layout()
|
|
766
1080
|
|
|
767
1081
|
|
|
768
1082
|
def plot_i2c_decode(
|