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
|
@@ -66,13 +66,13 @@ class ManchesterDecoder(AsyncDecoder):
|
|
|
66
66
|
longname = "Manchester Encoding"
|
|
67
67
|
desc = "Manchester and Differential Manchester decoder"
|
|
68
68
|
|
|
69
|
-
channels = [
|
|
69
|
+
channels = [
|
|
70
70
|
ChannelDef("data", "DATA", "Manchester encoded data", required=True),
|
|
71
71
|
]
|
|
72
72
|
|
|
73
|
-
optional_channels = []
|
|
73
|
+
optional_channels = []
|
|
74
74
|
|
|
75
|
-
options = [
|
|
75
|
+
options = [
|
|
76
76
|
OptionDef("bit_rate", "Bit rate", "Bits per second", default=10000000, values=None),
|
|
77
77
|
OptionDef(
|
|
78
78
|
"mode",
|
|
@@ -83,7 +83,7 @@ class ManchesterDecoder(AsyncDecoder):
|
|
|
83
83
|
),
|
|
84
84
|
]
|
|
85
85
|
|
|
86
|
-
annotations = [
|
|
86
|
+
annotations = [
|
|
87
87
|
("bit", "Decoded bit"),
|
|
88
88
|
("clock", "Recovered clock"),
|
|
89
89
|
("violation", "Encoding violation"),
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
from dataclasses import dataclass
|
|
21
21
|
from enum import Enum
|
|
22
|
-
from typing import TYPE_CHECKING
|
|
22
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
23
23
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
|
|
@@ -42,6 +42,17 @@ if TYPE_CHECKING:
|
|
|
42
42
|
from numpy.typing import NDArray
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
class _OneWireDecoderState(TypedDict):
|
|
46
|
+
"""State dictionary for 1-Wire decoder."""
|
|
47
|
+
|
|
48
|
+
decoded_bytes: list[int]
|
|
49
|
+
current_bits: list[int]
|
|
50
|
+
rom_id: OneWireROMID | None
|
|
51
|
+
rom_command: int | None
|
|
52
|
+
errors: list[str]
|
|
53
|
+
transaction_start: float
|
|
54
|
+
|
|
55
|
+
|
|
45
56
|
class OneWireMode(Enum):
|
|
46
57
|
"""1-Wire speed modes."""
|
|
47
58
|
|
|
@@ -203,13 +214,13 @@ class OneWireDecoder(AsyncDecoder):
|
|
|
203
214
|
longname = "Dallas/Maxim 1-Wire Protocol"
|
|
204
215
|
desc = "1-Wire bus decoder with ROM ID extraction"
|
|
205
216
|
|
|
206
|
-
channels = [
|
|
217
|
+
channels = [
|
|
207
218
|
ChannelDef("data", "DQ", "1-Wire data line", required=True),
|
|
208
219
|
]
|
|
209
220
|
|
|
210
|
-
optional_channels = []
|
|
221
|
+
optional_channels = []
|
|
211
222
|
|
|
212
|
-
options = [
|
|
223
|
+
options = [
|
|
213
224
|
OptionDef(
|
|
214
225
|
"mode",
|
|
215
226
|
"Speed mode",
|
|
@@ -226,7 +237,7 @@ class OneWireDecoder(AsyncDecoder):
|
|
|
226
237
|
),
|
|
227
238
|
]
|
|
228
239
|
|
|
229
|
-
annotations = [
|
|
240
|
+
annotations = [
|
|
230
241
|
("reset", "Reset pulse"),
|
|
231
242
|
("presence", "Presence pulse"),
|
|
232
243
|
("bit", "Data bit"),
|
|
@@ -273,172 +284,265 @@ class OneWireDecoder(AsyncDecoder):
|
|
|
273
284
|
>>> for packet in decoder.decode(trace):
|
|
274
285
|
... print(f"Command: {packet.annotations.get('rom_command')}")
|
|
275
286
|
"""
|
|
276
|
-
|
|
287
|
+
digital_trace = self._convert_to_digital(trace)
|
|
288
|
+
data, sample_rate = self._extract_trace_data(digital_trace)
|
|
289
|
+
us_to_samples = sample_rate / 1_000_000
|
|
290
|
+
|
|
291
|
+
falling, rising = self._find_edges(data)
|
|
292
|
+
if len(falling) == 0:
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
state = self._init_decoder_state(falling[0] / sample_rate)
|
|
296
|
+
|
|
297
|
+
for i in range(len(falling)):
|
|
298
|
+
result = self._process_edge(falling, rising, i, sample_rate, us_to_samples, state)
|
|
299
|
+
|
|
300
|
+
if result is not None: # Reset pulse yielded packet
|
|
301
|
+
yield result
|
|
302
|
+
|
|
303
|
+
# Yield final transaction if any
|
|
304
|
+
if state["decoded_bytes"]:
|
|
305
|
+
yield self._create_packet(state)
|
|
306
|
+
|
|
307
|
+
def _convert_to_digital(self, trace: DigitalTrace | WaveformTrace) -> DigitalTrace:
|
|
308
|
+
"""Convert trace to digital if needed."""
|
|
277
309
|
if isinstance(trace, WaveformTrace):
|
|
278
310
|
from oscura.analyzers.digital.extraction import to_digital
|
|
279
311
|
|
|
280
312
|
threshold = self._threshold if self._threshold != "auto" else "auto"
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
digital_trace = trace
|
|
313
|
+
return to_digital(trace, threshold=threshold) # type: ignore[arg-type]
|
|
314
|
+
return trace
|
|
284
315
|
|
|
316
|
+
def _extract_trace_data(self, digital_trace: DigitalTrace) -> tuple[NDArray[np.bool_], float]:
|
|
317
|
+
"""Extract data and sample rate from digital trace."""
|
|
285
318
|
data = digital_trace.data.astype(bool)
|
|
286
319
|
sample_rate = digital_trace.metadata.sample_rate
|
|
320
|
+
return data, sample_rate
|
|
287
321
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# Find all falling and rising edges
|
|
322
|
+
def _find_edges(self, data: NDArray[np.bool_]) -> tuple[NDArray[np.intp], NDArray[np.intp]]:
|
|
323
|
+
"""Find falling and rising edges in digital data."""
|
|
292
324
|
falling = np.where((data[:-1]) & (~data[1:]))[0]
|
|
293
325
|
rising = np.where((~data[:-1]) & (data[1:]))[0]
|
|
326
|
+
return falling, rising
|
|
327
|
+
|
|
328
|
+
def _init_decoder_state(self, start_time: float) -> _OneWireDecoderState:
|
|
329
|
+
"""Initialize decoder state machine."""
|
|
330
|
+
state: _OneWireDecoderState = {
|
|
331
|
+
"decoded_bytes": [],
|
|
332
|
+
"current_bits": [],
|
|
333
|
+
"rom_id": None,
|
|
334
|
+
"rom_command": None,
|
|
335
|
+
"errors": [],
|
|
336
|
+
"transaction_start": start_time,
|
|
337
|
+
}
|
|
338
|
+
return state
|
|
294
339
|
|
|
295
|
-
|
|
340
|
+
def _process_edge(
|
|
341
|
+
self,
|
|
342
|
+
falling: NDArray[np.intp],
|
|
343
|
+
rising: NDArray[np.intp],
|
|
344
|
+
i: int,
|
|
345
|
+
sample_rate: float,
|
|
346
|
+
us_to_samples: float,
|
|
347
|
+
state: _OneWireDecoderState,
|
|
348
|
+
) -> ProtocolPacket | None:
|
|
349
|
+
"""Process single edge, return packet if reset pulse detected."""
|
|
350
|
+
fall_idx = falling[i]
|
|
351
|
+
fall_time = fall_idx / sample_rate
|
|
352
|
+
|
|
353
|
+
rise_idx = self._find_rising_edge(rising, fall_idx)
|
|
354
|
+
if rise_idx is None:
|
|
355
|
+
return None
|
|
356
|
+
|
|
357
|
+
low_duration_us = (rise_idx - fall_idx) / us_to_samples
|
|
358
|
+
|
|
359
|
+
if self._is_reset_pulse(low_duration_us):
|
|
360
|
+
return self._handle_reset_pulse(
|
|
361
|
+
state, fall_time, rise_idx, sample_rate, falling, rising, us_to_samples
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
self._handle_data_bit(low_duration_us, fall_time, rise_idx, sample_rate, state)
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def _find_rising_edge(self, rising: NDArray[np.intp], fall_idx: int) -> int | None:
|
|
368
|
+
"""Find rising edge corresponding to falling edge."""
|
|
369
|
+
rise_candidates = rising[rising > fall_idx]
|
|
370
|
+
if len(rise_candidates) == 0:
|
|
371
|
+
return None
|
|
372
|
+
return int(rise_candidates[0])
|
|
373
|
+
|
|
374
|
+
def _is_reset_pulse(self, low_duration_us: float) -> bool:
|
|
375
|
+
"""Check if pulse duration indicates reset."""
|
|
376
|
+
return low_duration_us >= self._timings.reset_min * 0.8
|
|
377
|
+
|
|
378
|
+
def _handle_reset_pulse(
|
|
379
|
+
self,
|
|
380
|
+
state: _OneWireDecoderState,
|
|
381
|
+
fall_time: float,
|
|
382
|
+
rise_idx: int,
|
|
383
|
+
sample_rate: float,
|
|
384
|
+
falling: NDArray[np.intp],
|
|
385
|
+
rising: NDArray[np.intp],
|
|
386
|
+
us_to_samples: float,
|
|
387
|
+
) -> ProtocolPacket | None:
|
|
388
|
+
"""Handle reset pulse and look for presence."""
|
|
389
|
+
packet = None
|
|
390
|
+
if state["decoded_bytes"]:
|
|
391
|
+
packet = self._create_packet(state)
|
|
392
|
+
|
|
393
|
+
# Reset state
|
|
394
|
+
state["transaction_start"] = fall_time
|
|
395
|
+
state["decoded_bytes"] = []
|
|
396
|
+
state["current_bits"] = []
|
|
397
|
+
state["rom_id"] = None
|
|
398
|
+
state["rom_command"] = None
|
|
399
|
+
state["errors"] = []
|
|
400
|
+
|
|
401
|
+
self.put_annotation(fall_time, rise_idx / sample_rate, AnnotationLevel.BITS, "Reset")
|
|
402
|
+
self._check_presence_pulse(rise_idx, sample_rate, falling, rising, us_to_samples)
|
|
403
|
+
|
|
404
|
+
return packet
|
|
405
|
+
|
|
406
|
+
def _check_presence_pulse(
|
|
407
|
+
self,
|
|
408
|
+
rise_idx: int,
|
|
409
|
+
sample_rate: float,
|
|
410
|
+
falling: NDArray[np.intp],
|
|
411
|
+
rising: NDArray[np.intp],
|
|
412
|
+
us_to_samples: float,
|
|
413
|
+
) -> None:
|
|
414
|
+
"""Check for presence pulse after reset."""
|
|
415
|
+
next_falls = falling[falling > rise_idx]
|
|
416
|
+
if len(next_falls) == 0:
|
|
296
417
|
return
|
|
297
418
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
rom_command: int | None = None
|
|
303
|
-
errors: list[str] = []
|
|
304
|
-
transaction_start: float = falling[0] / sample_rate
|
|
305
|
-
|
|
306
|
-
i = 0
|
|
307
|
-
while i < len(falling):
|
|
308
|
-
fall_idx = falling[i]
|
|
309
|
-
fall_time = fall_idx / sample_rate
|
|
310
|
-
|
|
311
|
-
# Find corresponding rising edge
|
|
312
|
-
rise_candidates = rising[rising > fall_idx]
|
|
313
|
-
if len(rise_candidates) == 0:
|
|
314
|
-
break
|
|
315
|
-
|
|
316
|
-
rise_idx = rise_candidates[0]
|
|
317
|
-
low_duration_us = (rise_idx - fall_idx) / us_to_samples
|
|
318
|
-
|
|
319
|
-
# Check for reset pulse
|
|
320
|
-
if low_duration_us >= self._timings.reset_min * 0.8:
|
|
321
|
-
# This is a reset pulse
|
|
322
|
-
if decoded_bytes:
|
|
323
|
-
# Yield previous transaction
|
|
324
|
-
annotations = self._build_annotations(decoded_bytes, rom_command, rom_id)
|
|
325
|
-
yield ProtocolPacket(
|
|
326
|
-
timestamp=transaction_start,
|
|
327
|
-
protocol="1-wire",
|
|
328
|
-
data=bytes(decoded_bytes),
|
|
329
|
-
annotations=annotations,
|
|
330
|
-
errors=errors.copy() if errors else None, # type: ignore[arg-type]
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
# Start new transaction
|
|
334
|
-
transaction_start = fall_time
|
|
335
|
-
decoded_bytes = []
|
|
336
|
-
current_bits = []
|
|
337
|
-
rom_id = None
|
|
338
|
-
rom_command = None
|
|
339
|
-
errors = []
|
|
340
|
-
|
|
341
|
-
self.put_annotation(
|
|
342
|
-
fall_time,
|
|
343
|
-
rise_idx / sample_rate,
|
|
344
|
-
AnnotationLevel.BITS,
|
|
345
|
-
"Reset",
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
# Look for presence pulse (pulled low by slave)
|
|
349
|
-
next_falls = falling[falling > rise_idx]
|
|
350
|
-
if len(next_falls) > 0:
|
|
351
|
-
next_fall = next_falls[0]
|
|
352
|
-
wait_time_us = (next_fall - rise_idx) / us_to_samples
|
|
353
|
-
if wait_time_us < self._timings.presence_max * 2:
|
|
354
|
-
# Found presence response
|
|
355
|
-
next_rises = rising[rising > next_fall]
|
|
356
|
-
if len(next_rises) > 0:
|
|
357
|
-
presence_end = next_rises[0]
|
|
358
|
-
presence_us = (presence_end - next_fall) / us_to_samples
|
|
359
|
-
if (
|
|
360
|
-
self._timings.presence_min * 0.5
|
|
361
|
-
<= presence_us
|
|
362
|
-
<= self._timings.presence_max * 1.5
|
|
363
|
-
):
|
|
364
|
-
self.put_annotation(
|
|
365
|
-
next_fall / sample_rate,
|
|
366
|
-
presence_end / sample_rate,
|
|
367
|
-
AnnotationLevel.BITS,
|
|
368
|
-
"Presence",
|
|
369
|
-
)
|
|
370
|
-
i += 1
|
|
371
|
-
continue
|
|
372
|
-
|
|
373
|
-
# Data bit - determine if 0 or 1
|
|
374
|
-
# Short low pulse = write 1 or read 1
|
|
375
|
-
# Long low pulse = write 0 or read 0
|
|
376
|
-
if low_duration_us < self._timings.write_1_low_max * 2:
|
|
377
|
-
bit = 1
|
|
378
|
-
elif low_duration_us >= self._timings.write_0_low_min * 0.5:
|
|
379
|
-
bit = 0
|
|
380
|
-
else:
|
|
381
|
-
# Ambiguous timing
|
|
382
|
-
bit = 1 if low_duration_us < self._timings.slot_min * 0.5 else 0
|
|
383
|
-
|
|
384
|
-
current_bits.append(bit)
|
|
385
|
-
|
|
386
|
-
# Assemble bytes (LSB first)
|
|
387
|
-
if len(current_bits) == 8:
|
|
388
|
-
byte_val = sum(b << i for i, b in enumerate(current_bits))
|
|
389
|
-
decoded_bytes.append(byte_val)
|
|
390
|
-
current_bits = []
|
|
391
|
-
|
|
392
|
-
# Check for ROM command (first byte after reset)
|
|
393
|
-
if len(decoded_bytes) == 1:
|
|
394
|
-
rom_command = byte_val
|
|
395
|
-
cmd_name = ROM_COMMAND_NAMES.get(byte_val, f"Unknown (0x{byte_val:02X})")
|
|
396
|
-
self.put_annotation(
|
|
397
|
-
fall_time,
|
|
398
|
-
rise_idx / sample_rate,
|
|
399
|
-
AnnotationLevel.BYTES,
|
|
400
|
-
f"ROM Cmd: {cmd_name}",
|
|
401
|
-
)
|
|
402
|
-
|
|
403
|
-
# Check for ROM ID (8 bytes after ROM command)
|
|
404
|
-
if len(decoded_bytes) == 9 and rom_command in (
|
|
405
|
-
OneWireROMCommand.READ_ROM.value,
|
|
406
|
-
OneWireROMCommand.MATCH_ROM.value,
|
|
407
|
-
):
|
|
408
|
-
try:
|
|
409
|
-
rom_id = OneWireROMID.from_bytes(bytes(decoded_bytes[1:9]))
|
|
410
|
-
if not rom_id.verify_crc():
|
|
411
|
-
errors.append("ROM ID CRC error")
|
|
412
|
-
self.put_annotation(
|
|
413
|
-
transaction_start,
|
|
414
|
-
rise_idx / sample_rate,
|
|
415
|
-
AnnotationLevel.BYTES,
|
|
416
|
-
f"ROM: {rom_id.to_hex()}",
|
|
417
|
-
)
|
|
418
|
-
except ValueError as e:
|
|
419
|
-
errors.append(f"ROM parse error: {e}")
|
|
420
|
-
|
|
421
|
-
i += 1
|
|
419
|
+
next_fall = next_falls[0]
|
|
420
|
+
wait_time_us = (next_fall - rise_idx) / us_to_samples
|
|
421
|
+
if wait_time_us >= self._timings.presence_max * 2:
|
|
422
|
+
return
|
|
422
423
|
|
|
423
|
-
|
|
424
|
-
if
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
424
|
+
next_rises = rising[rising > next_fall]
|
|
425
|
+
if len(next_rises) == 0:
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
presence_end = next_rises[0]
|
|
429
|
+
presence_us = (presence_end - next_fall) / us_to_samples
|
|
430
|
+
if self._timings.presence_min * 0.5 <= presence_us <= self._timings.presence_max * 1.5:
|
|
431
|
+
self.put_annotation(
|
|
432
|
+
next_fall / sample_rate,
|
|
433
|
+
presence_end / sample_rate,
|
|
434
|
+
AnnotationLevel.BITS,
|
|
435
|
+
"Presence",
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
def _handle_data_bit(
|
|
439
|
+
self,
|
|
440
|
+
low_duration_us: float,
|
|
441
|
+
fall_time: float,
|
|
442
|
+
rise_idx: int,
|
|
443
|
+
sample_rate: float,
|
|
444
|
+
state: _OneWireDecoderState,
|
|
445
|
+
) -> None:
|
|
446
|
+
"""Decode and process data bit."""
|
|
447
|
+
bit = self._decode_bit(low_duration_us)
|
|
448
|
+
state["current_bits"].append(bit)
|
|
449
|
+
|
|
450
|
+
if len(state["current_bits"]) == 8:
|
|
451
|
+
self._process_complete_byte(state, fall_time, rise_idx, sample_rate)
|
|
452
|
+
|
|
453
|
+
def _decode_bit(self, low_duration_us: float) -> int:
|
|
454
|
+
"""Decode bit from pulse duration."""
|
|
455
|
+
if low_duration_us < self._timings.write_1_low_max * 2:
|
|
456
|
+
return 1
|
|
457
|
+
elif low_duration_us >= self._timings.write_0_low_min * 0.5:
|
|
458
|
+
return 0
|
|
459
|
+
else:
|
|
460
|
+
return 1 if low_duration_us < self._timings.slot_min * 0.5 else 0
|
|
461
|
+
|
|
462
|
+
def _process_complete_byte(
|
|
463
|
+
self,
|
|
464
|
+
state: _OneWireDecoderState,
|
|
465
|
+
fall_time: float,
|
|
466
|
+
rise_idx: int,
|
|
467
|
+
sample_rate: float,
|
|
468
|
+
) -> None:
|
|
469
|
+
"""Process complete 8-bit byte."""
|
|
470
|
+
byte_val = sum(b << i for i, b in enumerate(state["current_bits"]))
|
|
471
|
+
state["decoded_bytes"].append(byte_val)
|
|
472
|
+
state["current_bits"] = []
|
|
473
|
+
|
|
474
|
+
if len(state["decoded_bytes"]) == 1:
|
|
475
|
+
self._handle_rom_command(byte_val, fall_time, rise_idx, sample_rate, state)
|
|
476
|
+
elif len(state["decoded_bytes"]) == 9:
|
|
477
|
+
self._handle_rom_id(state, rise_idx, sample_rate)
|
|
478
|
+
|
|
479
|
+
def _handle_rom_command(
|
|
480
|
+
self,
|
|
481
|
+
byte_val: int,
|
|
482
|
+
fall_time: float,
|
|
483
|
+
rise_idx: int,
|
|
484
|
+
sample_rate: float,
|
|
485
|
+
state: _OneWireDecoderState,
|
|
486
|
+
) -> None:
|
|
487
|
+
"""Handle ROM command (first byte)."""
|
|
488
|
+
state["rom_command"] = byte_val
|
|
489
|
+
cmd_name = ROM_COMMAND_NAMES.get(byte_val, f"Unknown (0x{byte_val:02X})")
|
|
490
|
+
self.put_annotation(
|
|
491
|
+
fall_time,
|
|
492
|
+
rise_idx / sample_rate,
|
|
493
|
+
AnnotationLevel.BYTES,
|
|
494
|
+
f"ROM Cmd: {cmd_name}",
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
def _handle_rom_id(
|
|
498
|
+
self,
|
|
499
|
+
state: _OneWireDecoderState,
|
|
500
|
+
rise_idx: int,
|
|
501
|
+
sample_rate: float,
|
|
502
|
+
) -> None:
|
|
503
|
+
"""Handle ROM ID (8 bytes after ROM command)."""
|
|
504
|
+
rom_command = state["rom_command"]
|
|
505
|
+
if rom_command not in (
|
|
506
|
+
OneWireROMCommand.READ_ROM.value,
|
|
507
|
+
OneWireROMCommand.MATCH_ROM.value,
|
|
508
|
+
):
|
|
509
|
+
return
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
rom_id = OneWireROMID.from_bytes(bytes(state["decoded_bytes"][1:9]))
|
|
513
|
+
if not rom_id.verify_crc():
|
|
514
|
+
state["errors"].append("ROM ID CRC error")
|
|
515
|
+
state["rom_id"] = rom_id
|
|
516
|
+
self.put_annotation(
|
|
517
|
+
state["transaction_start"],
|
|
518
|
+
rise_idx / sample_rate,
|
|
519
|
+
AnnotationLevel.BYTES,
|
|
520
|
+
f"ROM: {rom_id.to_hex()}",
|
|
432
521
|
)
|
|
522
|
+
except ValueError as e:
|
|
523
|
+
state["errors"].append(f"ROM parse error: {e}")
|
|
524
|
+
|
|
525
|
+
def _create_packet(self, state: _OneWireDecoderState) -> ProtocolPacket:
|
|
526
|
+
"""Create protocol packet from decoder state."""
|
|
527
|
+
annotations = self._build_annotations(
|
|
528
|
+
state["decoded_bytes"], state["rom_command"], state["rom_id"]
|
|
529
|
+
)
|
|
530
|
+
return ProtocolPacket(
|
|
531
|
+
timestamp=state["transaction_start"],
|
|
532
|
+
protocol="1-wire",
|
|
533
|
+
data=bytes(state["decoded_bytes"]),
|
|
534
|
+
annotations=annotations,
|
|
535
|
+
errors=state["errors"].copy() if state["errors"] else None, # type: ignore[arg-type]
|
|
536
|
+
)
|
|
433
537
|
|
|
434
538
|
def _build_annotations(
|
|
435
539
|
self,
|
|
436
540
|
decoded_bytes: list[int],
|
|
437
541
|
rom_command: int | None,
|
|
438
542
|
rom_id: OneWireROMID | None,
|
|
439
|
-
) -> dict
|
|
543
|
+
) -> dict[str, object]:
|
|
440
544
|
"""Build annotation dictionary for packet."""
|
|
441
|
-
annotations: dict = {
|
|
545
|
+
annotations: dict[str, object] = {
|
|
442
546
|
"mode": self._mode.value,
|
|
443
547
|
"byte_count": len(decoded_bytes),
|
|
444
548
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Parallel bus protocol decoders.
|
|
2
|
+
|
|
3
|
+
This module provides decoders for parallel bus protocols including:
|
|
4
|
+
- GPIB (IEEE-488): General Purpose Interface Bus for instrument control
|
|
5
|
+
- Centronics: Parallel printer interface
|
|
6
|
+
- ISA: Industry Standard Architecture bus
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.analyzers.protocols.parallel_bus import decode_gpib
|
|
10
|
+
>>> frames = decode_gpib(dio_lines, dav, nrfd, ndac, eoi, atn, sample_rate)
|
|
11
|
+
>>> for frame in frames:
|
|
12
|
+
... print(f"Type: {frame.message_type}, Data: 0x{frame.data:02X}")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from oscura.analyzers.protocols.parallel_bus.centronics import decode_centronics
|
|
18
|
+
from oscura.analyzers.protocols.parallel_bus.gpib import decode_gpib
|
|
19
|
+
|
|
20
|
+
__all__ = ["decode_centronics", "decode_gpib"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Centronics parallel printer protocol decoder.
|
|
2
|
+
|
|
3
|
+
Decoder for the Centronics parallel printer interface, commonly used
|
|
4
|
+
in legacy printer connections (pre-USB).
|
|
5
|
+
|
|
6
|
+
Protocol Overview:
|
|
7
|
+
- 8 data lines (D0-D7)
|
|
8
|
+
- STROBE: Latches data (active low)
|
|
9
|
+
- BUSY: Printer busy signal (active high)
|
|
10
|
+
- ACK: Acknowledge data received (active low)
|
|
11
|
+
|
|
12
|
+
References:
|
|
13
|
+
- Centronics Data Computer Corp. Parallel Interface Standard
|
|
14
|
+
- IEEE 1284-1994 (modern parallel port standard based on Centronics)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from numpy.typing import NDArray
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class CentronicsFrame:
|
|
30
|
+
"""Decoded Centronics frame.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
timestamp: Frame timestamp in seconds
|
|
34
|
+
data: Data byte value
|
|
35
|
+
character: ASCII character if printable, None otherwise
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
timestamp: float
|
|
39
|
+
data: int
|
|
40
|
+
character: str | None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def decode_centronics(
|
|
44
|
+
data_lines: list[NDArray[np.bool_]],
|
|
45
|
+
strobe: NDArray[np.bool_],
|
|
46
|
+
busy: NDArray[np.bool_],
|
|
47
|
+
ack: NDArray[np.bool_],
|
|
48
|
+
sample_rate: float,
|
|
49
|
+
) -> list[CentronicsFrame]:
|
|
50
|
+
"""Decode Centronics parallel printer protocol.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
data_lines: List of 8 data line arrays (D0-D7)
|
|
54
|
+
strobe: Strobe signal (active low, latches data)
|
|
55
|
+
busy: Busy signal (active high when printer busy)
|
|
56
|
+
ack: Acknowledge signal (active low when data accepted)
|
|
57
|
+
sample_rate: Sample rate in Hz
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of decoded Centronics frames
|
|
61
|
+
|
|
62
|
+
Example:
|
|
63
|
+
>>> frames = decode_centronics(data_lines, strobe, busy, ack, 1e6)
|
|
64
|
+
>>> text = ''.join(f.character or f'\\x{f.data:02x}' for f in frames)
|
|
65
|
+
>>> print(f"Printer data: {text}")
|
|
66
|
+
"""
|
|
67
|
+
frames: list[CentronicsFrame] = []
|
|
68
|
+
|
|
69
|
+
# Find STROBE falling edges (data latch events)
|
|
70
|
+
strobe_falling = np.where((strobe[:-1] == True) & (strobe[1:] == False))[0] # noqa: E712
|
|
71
|
+
|
|
72
|
+
for edge_idx in strobe_falling:
|
|
73
|
+
# Sample data at STROBE falling edge
|
|
74
|
+
data_byte = 0
|
|
75
|
+
for bit_idx, data_line in enumerate(data_lines):
|
|
76
|
+
if edge_idx < len(data_line) and data_line[edge_idx]:
|
|
77
|
+
data_byte |= 1 << bit_idx
|
|
78
|
+
|
|
79
|
+
timestamp = edge_idx / sample_rate
|
|
80
|
+
|
|
81
|
+
# Convert to character if printable ASCII
|
|
82
|
+
character = chr(data_byte) if 32 <= data_byte < 127 else None
|
|
83
|
+
|
|
84
|
+
frame = CentronicsFrame(
|
|
85
|
+
timestamp=timestamp,
|
|
86
|
+
data=data_byte,
|
|
87
|
+
character=character,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
frames.append(frame)
|
|
91
|
+
|
|
92
|
+
return frames
|