oscura 0.0.1__py3-none-any.whl → 0.1.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 +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""I2C protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module provides I2C (Inter-Integrated Circuit) protocol decoding
|
|
4
|
+
with ACK/NAK detection, arbitration monitoring, and multi-speed support.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.i2c import I2CDecoder
|
|
9
|
+
>>> decoder = I2CDecoder()
|
|
10
|
+
>>> for packet in decoder.decode(sda=sda, scl=scl):
|
|
11
|
+
... print(f"Address: 0x{packet.annotations['address']:02X}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
I2C Specification (NXP UM10204)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
|
|
25
|
+
from oscura.analyzers.protocols.base import (
|
|
26
|
+
AnnotationLevel,
|
|
27
|
+
ChannelDef,
|
|
28
|
+
OptionDef,
|
|
29
|
+
SyncDecoder,
|
|
30
|
+
)
|
|
31
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from collections.abc import Iterator
|
|
35
|
+
|
|
36
|
+
from numpy.typing import NDArray
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class I2CCondition(Enum):
|
|
40
|
+
"""I2C bus conditions."""
|
|
41
|
+
|
|
42
|
+
START = "start"
|
|
43
|
+
STOP = "stop"
|
|
44
|
+
REPEATED_START = "repeated_start"
|
|
45
|
+
ACK = "ack"
|
|
46
|
+
NAK = "nak"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class I2CTransaction:
|
|
51
|
+
"""I2C transaction record.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
address: 7-bit or 10-bit device address.
|
|
55
|
+
read: True for read, False for write.
|
|
56
|
+
data: Data bytes transferred.
|
|
57
|
+
acks: List of ACK (True) / NAK (False) for each byte.
|
|
58
|
+
errors: List of detected errors.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
address: int
|
|
62
|
+
read: bool
|
|
63
|
+
data: list[int]
|
|
64
|
+
acks: list[bool]
|
|
65
|
+
errors: list[str]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class I2CDecoder(SyncDecoder):
|
|
69
|
+
"""I2C protocol decoder.
|
|
70
|
+
|
|
71
|
+
Decodes I2C bus transactions with ACK/NAK detection,
|
|
72
|
+
arbitration monitoring, and support for standard, fast,
|
|
73
|
+
and high-speed modes.
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> decoder = I2CDecoder()
|
|
77
|
+
>>> for packet in decoder.decode(sda=sda, scl=scl, sample_rate=10e6):
|
|
78
|
+
... print(f"Addr: 0x{packet.annotations['address']:02X}")
|
|
79
|
+
... print(f"Data: {packet.data.hex()}")
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
id = "i2c"
|
|
83
|
+
name = "I2C"
|
|
84
|
+
longname = "Inter-Integrated Circuit"
|
|
85
|
+
desc = "I2C bus protocol decoder"
|
|
86
|
+
|
|
87
|
+
channels = [ # noqa: RUF012
|
|
88
|
+
ChannelDef("scl", "SCL", "Clock line", required=True),
|
|
89
|
+
ChannelDef("sda", "SDA", "Data line", required=True),
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
optional_channels = [] # noqa: RUF012
|
|
93
|
+
|
|
94
|
+
options = [ # noqa: RUF012
|
|
95
|
+
OptionDef(
|
|
96
|
+
"address_format",
|
|
97
|
+
"Address format",
|
|
98
|
+
"7-bit or 10-bit",
|
|
99
|
+
default="auto",
|
|
100
|
+
values=["auto", "7bit", "10bit"],
|
|
101
|
+
),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
annotations = [ # noqa: RUF012
|
|
105
|
+
("start", "Start condition"),
|
|
106
|
+
("stop", "Stop condition"),
|
|
107
|
+
("address", "Device address"),
|
|
108
|
+
("data", "Data byte"),
|
|
109
|
+
("ack", "ACK"),
|
|
110
|
+
("nak", "NAK"),
|
|
111
|
+
("error", "Error"),
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
address_format: str = "auto",
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Initialize I2C decoder.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
address_format: Address format ("auto", "7bit", "10bit").
|
|
122
|
+
"""
|
|
123
|
+
super().__init__(address_format=address_format)
|
|
124
|
+
self._address_format = address_format
|
|
125
|
+
|
|
126
|
+
def decode( # type: ignore[override]
|
|
127
|
+
self,
|
|
128
|
+
trace: DigitalTrace | None = None,
|
|
129
|
+
*,
|
|
130
|
+
scl: NDArray[np.bool_] | None = None,
|
|
131
|
+
sda: NDArray[np.bool_] | None = None,
|
|
132
|
+
sample_rate: float = 1.0,
|
|
133
|
+
) -> Iterator[ProtocolPacket]:
|
|
134
|
+
"""Decode I2C transactions.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
trace: Optional primary trace.
|
|
138
|
+
scl: Clock signal.
|
|
139
|
+
sda: Data signal.
|
|
140
|
+
sample_rate: Sample rate in Hz.
|
|
141
|
+
|
|
142
|
+
Yields:
|
|
143
|
+
Decoded I2C transactions as ProtocolPacket objects.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> decoder = I2CDecoder()
|
|
147
|
+
>>> for pkt in decoder.decode(scl=scl, sda=sda, sample_rate=10e6):
|
|
148
|
+
... print(f"Address: 0x{pkt.annotations['address']:02X}")
|
|
149
|
+
"""
|
|
150
|
+
if scl is None or sda is None:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
n_samples = min(len(scl), len(sda))
|
|
154
|
+
scl = scl[:n_samples]
|
|
155
|
+
sda = sda[:n_samples]
|
|
156
|
+
|
|
157
|
+
# Find start and stop conditions
|
|
158
|
+
# START: SDA falls while SCL is high
|
|
159
|
+
# STOP: SDA rises while SCL is high
|
|
160
|
+
|
|
161
|
+
conditions = []
|
|
162
|
+
|
|
163
|
+
for i in range(1, n_samples):
|
|
164
|
+
if scl[i] and scl[i - 1]: # SCL is high
|
|
165
|
+
if sda[i - 1] and not sda[i]: # SDA falling
|
|
166
|
+
conditions.append((i, I2CCondition.START))
|
|
167
|
+
elif not sda[i - 1] and sda[i]: # SDA rising
|
|
168
|
+
conditions.append((i, I2CCondition.STOP))
|
|
169
|
+
|
|
170
|
+
if len(conditions) == 0:
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
# Process transactions between START and STOP
|
|
174
|
+
trans_idx = 0
|
|
175
|
+
i = 0
|
|
176
|
+
|
|
177
|
+
while i < len(conditions):
|
|
178
|
+
if conditions[i][1] != I2CCondition.START:
|
|
179
|
+
i += 1
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
start_idx = conditions[i][0]
|
|
183
|
+
start_time = start_idx / sample_rate
|
|
184
|
+
|
|
185
|
+
# Find corresponding STOP or next START
|
|
186
|
+
end_cond_idx = i + 1
|
|
187
|
+
while end_cond_idx < len(conditions):
|
|
188
|
+
if conditions[end_cond_idx][1] == I2CCondition.STOP:
|
|
189
|
+
break
|
|
190
|
+
if conditions[end_cond_idx][1] == I2CCondition.START:
|
|
191
|
+
# Repeated START
|
|
192
|
+
break
|
|
193
|
+
end_cond_idx += 1
|
|
194
|
+
|
|
195
|
+
if end_cond_idx >= len(conditions):
|
|
196
|
+
break
|
|
197
|
+
|
|
198
|
+
end_idx = conditions[end_cond_idx][0]
|
|
199
|
+
is_repeated = conditions[end_cond_idx][1] == I2CCondition.START
|
|
200
|
+
|
|
201
|
+
# Extract bytes from this transaction
|
|
202
|
+
bytes_data, acks = self._extract_bytes(
|
|
203
|
+
scl[start_idx:end_idx],
|
|
204
|
+
sda[start_idx:end_idx],
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if len(bytes_data) == 0:
|
|
208
|
+
i = end_cond_idx
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
# First byte is address + R/W
|
|
212
|
+
address_byte = bytes_data[0]
|
|
213
|
+
address = address_byte >> 1
|
|
214
|
+
is_read = (address_byte & 1) == 1
|
|
215
|
+
|
|
216
|
+
# Check for 10-bit address
|
|
217
|
+
is_10bit = False
|
|
218
|
+
actual_address = address
|
|
219
|
+
|
|
220
|
+
if self._address_format == "10bit" or (
|
|
221
|
+
self._address_format == "auto" and (address_byte >> 3) == 0b11110
|
|
222
|
+
):
|
|
223
|
+
# 10-bit address format
|
|
224
|
+
if len(bytes_data) >= 2:
|
|
225
|
+
is_10bit = True
|
|
226
|
+
high_bits = (address_byte >> 1) & 0b11
|
|
227
|
+
low_bits = bytes_data[1]
|
|
228
|
+
actual_address = (high_bits << 8) | low_bits
|
|
229
|
+
data_bytes = bytes_data[2:]
|
|
230
|
+
data_acks = acks[2:] if len(acks) > 2 else []
|
|
231
|
+
else:
|
|
232
|
+
data_bytes = []
|
|
233
|
+
data_acks = []
|
|
234
|
+
else:
|
|
235
|
+
actual_address = address
|
|
236
|
+
data_bytes = bytes_data[1:]
|
|
237
|
+
data_acks = acks[1:] if len(acks) > 1 else []
|
|
238
|
+
|
|
239
|
+
# Check for errors
|
|
240
|
+
errors = []
|
|
241
|
+
if len(acks) > 0 and not acks[0]:
|
|
242
|
+
errors.append("NAK on address")
|
|
243
|
+
|
|
244
|
+
for j, (_byte, ack) in enumerate(zip(data_bytes, data_acks, strict=False)):
|
|
245
|
+
if not ack and not is_read:
|
|
246
|
+
errors.append(f"NAK on byte {j}")
|
|
247
|
+
|
|
248
|
+
end_time = end_idx / sample_rate
|
|
249
|
+
|
|
250
|
+
# Add annotations
|
|
251
|
+
self.put_annotation(
|
|
252
|
+
start_time,
|
|
253
|
+
start_time + 1e-6,
|
|
254
|
+
AnnotationLevel.BITS,
|
|
255
|
+
"START" if not is_repeated else "Sr",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
addr_text = f"0x{actual_address:02X}" if not is_10bit else f"0x{actual_address:03X}"
|
|
259
|
+
self.put_annotation(
|
|
260
|
+
start_time,
|
|
261
|
+
end_time,
|
|
262
|
+
AnnotationLevel.FIELDS,
|
|
263
|
+
f"{addr_text} {'R' if is_read else 'W'}",
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Create packet
|
|
267
|
+
annotations = {
|
|
268
|
+
"address": actual_address,
|
|
269
|
+
"address_10bit": is_10bit,
|
|
270
|
+
"read": is_read,
|
|
271
|
+
"bytes": bytes_data,
|
|
272
|
+
"acks": acks,
|
|
273
|
+
"transaction_num": trans_idx,
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
packet = ProtocolPacket(
|
|
277
|
+
timestamp=start_time,
|
|
278
|
+
protocol="i2c",
|
|
279
|
+
data=bytes(data_bytes),
|
|
280
|
+
annotations=annotations,
|
|
281
|
+
errors=errors,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
yield packet
|
|
285
|
+
|
|
286
|
+
trans_idx += 1
|
|
287
|
+
i = end_cond_idx
|
|
288
|
+
|
|
289
|
+
if is_repeated:
|
|
290
|
+
continue
|
|
291
|
+
else:
|
|
292
|
+
i += 1
|
|
293
|
+
|
|
294
|
+
def _extract_bytes(
|
|
295
|
+
self,
|
|
296
|
+
scl: NDArray[np.bool_],
|
|
297
|
+
sda: NDArray[np.bool_],
|
|
298
|
+
) -> tuple[list[int], list[bool]]:
|
|
299
|
+
"""Extract bytes from I2C transaction.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
scl: Clock signal segment.
|
|
303
|
+
sda: Data signal segment.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
(bytes, acks) - List of byte values and ACK flags.
|
|
307
|
+
"""
|
|
308
|
+
# Find rising edges of SCL (data sampling points)
|
|
309
|
+
rising_edges = np.where(~scl[:-1] & scl[1:])[0] + 1
|
|
310
|
+
|
|
311
|
+
if len(rising_edges) < 9: # Need at least 8 data bits + ACK
|
|
312
|
+
return [], []
|
|
313
|
+
|
|
314
|
+
bytes_data = []
|
|
315
|
+
acks = []
|
|
316
|
+
|
|
317
|
+
i = 0
|
|
318
|
+
while i + 9 <= len(rising_edges):
|
|
319
|
+
# Extract 8 data bits (MSB first)
|
|
320
|
+
byte_val = 0
|
|
321
|
+
for bit_idx in range(8):
|
|
322
|
+
sample_idx = rising_edges[i + bit_idx]
|
|
323
|
+
if sample_idx < len(sda):
|
|
324
|
+
bit = 1 if sda[sample_idx] else 0
|
|
325
|
+
byte_val = (byte_val << 1) | bit
|
|
326
|
+
|
|
327
|
+
# Extract ACK bit (9th bit, low = ACK, high = NAK)
|
|
328
|
+
ack_idx = rising_edges[i + 8]
|
|
329
|
+
if ack_idx < len(sda):
|
|
330
|
+
ack = not sda[ack_idx] # Low = ACK
|
|
331
|
+
else:
|
|
332
|
+
ack = False
|
|
333
|
+
|
|
334
|
+
bytes_data.append(byte_val)
|
|
335
|
+
acks.append(ack)
|
|
336
|
+
|
|
337
|
+
i += 9
|
|
338
|
+
|
|
339
|
+
return bytes_data, acks
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def decode_i2c(
|
|
343
|
+
scl: NDArray[np.bool_],
|
|
344
|
+
sda: NDArray[np.bool_],
|
|
345
|
+
sample_rate: float = 1.0,
|
|
346
|
+
address_format: str = "auto",
|
|
347
|
+
) -> list[ProtocolPacket]:
|
|
348
|
+
"""Convenience function to decode I2C transactions.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
scl: Clock signal.
|
|
352
|
+
sda: Data signal.
|
|
353
|
+
sample_rate: Sample rate in Hz.
|
|
354
|
+
address_format: Address format ("auto", "7bit", "10bit").
|
|
355
|
+
|
|
356
|
+
Returns:
|
|
357
|
+
List of decoded I2C transactions.
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
>>> packets = decode_i2c(scl, sda, sample_rate=10e6)
|
|
361
|
+
>>> for pkt in packets:
|
|
362
|
+
... print(f"Address: 0x{pkt.annotations['address']:02X}")
|
|
363
|
+
"""
|
|
364
|
+
decoder = I2CDecoder(address_format=address_format)
|
|
365
|
+
return list(decoder.decode(scl=scl, sda=sda, sample_rate=sample_rate))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
__all__ = ["I2CCondition", "I2CDecoder", "I2CTransaction", "decode_i2c"]
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""I2S protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module provides Inter-IC Sound (I2S) audio protocol decoding
|
|
4
|
+
with support for standard, left-justified, and right-justified modes.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.i2s import I2SDecoder
|
|
9
|
+
>>> decoder = I2SDecoder(bit_depth=16)
|
|
10
|
+
>>> for packet in decoder.decode(bck=bck, ws=ws, sd=sd):
|
|
11
|
+
... print(f"Left: {packet.annotations['left_sample']}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
I2S Bus Specification (Philips Semiconductors)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import TYPE_CHECKING, Literal
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
from oscura.analyzers.protocols.base import (
|
|
25
|
+
AnnotationLevel,
|
|
26
|
+
ChannelDef,
|
|
27
|
+
OptionDef,
|
|
28
|
+
SyncDecoder,
|
|
29
|
+
)
|
|
30
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Iterator
|
|
34
|
+
|
|
35
|
+
from numpy.typing import NDArray
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class I2SMode(Enum):
|
|
39
|
+
"""I2S alignment modes."""
|
|
40
|
+
|
|
41
|
+
STANDARD = "standard" # MSB 1 clock after WS change
|
|
42
|
+
LEFT_JUSTIFIED = "left_justified" # MSB at WS change
|
|
43
|
+
RIGHT_JUSTIFIED = "right_justified" # MSB before WS change
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class I2SDecoder(SyncDecoder):
|
|
47
|
+
"""I2S protocol decoder.
|
|
48
|
+
|
|
49
|
+
Decodes I2S audio bus transactions with configurable bit depth
|
|
50
|
+
and alignment modes (standard, left-justified, right-justified).
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
id: "i2s"
|
|
54
|
+
name: "I2S"
|
|
55
|
+
channels: [bck, ws, sd] (required)
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> decoder = I2SDecoder(bit_depth=24, mode="standard")
|
|
59
|
+
>>> for packet in decoder.decode(bck=bck, ws=ws, sd=sd, sample_rate=1e6):
|
|
60
|
+
... print(f"Stereo: L={packet.annotations['left']} R={packet.annotations['right']}")
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
id = "i2s"
|
|
64
|
+
name = "I2S"
|
|
65
|
+
longname = "Inter-IC Sound"
|
|
66
|
+
desc = "I2S audio bus protocol decoder"
|
|
67
|
+
|
|
68
|
+
channels = [ # noqa: RUF012
|
|
69
|
+
ChannelDef("bck", "BCK", "Bit Clock (SCLK)", required=True),
|
|
70
|
+
ChannelDef("ws", "WS", "Word Select (LRCLK)", required=True),
|
|
71
|
+
ChannelDef("sd", "SD", "Serial Data", required=True),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
optional_channels = [] # noqa: RUF012
|
|
75
|
+
|
|
76
|
+
options = [ # noqa: RUF012
|
|
77
|
+
OptionDef(
|
|
78
|
+
"bit_depth",
|
|
79
|
+
"Bit depth",
|
|
80
|
+
"Bits per sample",
|
|
81
|
+
default=16,
|
|
82
|
+
values=[8, 16, 24, 32],
|
|
83
|
+
),
|
|
84
|
+
OptionDef(
|
|
85
|
+
"mode",
|
|
86
|
+
"Mode",
|
|
87
|
+
"Alignment mode",
|
|
88
|
+
default="standard",
|
|
89
|
+
values=["standard", "left_justified", "right_justified"],
|
|
90
|
+
),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
annotations = [ # noqa: RUF012
|
|
94
|
+
("left", "Left channel sample"),
|
|
95
|
+
("right", "Right channel sample"),
|
|
96
|
+
("word", "Word boundary"),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
def __init__(
|
|
100
|
+
self,
|
|
101
|
+
bit_depth: int = 16,
|
|
102
|
+
mode: Literal["standard", "left_justified", "right_justified"] = "standard",
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Initialize I2S decoder.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
bit_depth: Bits per sample (8, 16, 24, 32).
|
|
108
|
+
mode: Alignment mode.
|
|
109
|
+
"""
|
|
110
|
+
super().__init__(bit_depth=bit_depth, mode=mode)
|
|
111
|
+
self._bit_depth = bit_depth
|
|
112
|
+
self._mode = I2SMode(mode)
|
|
113
|
+
|
|
114
|
+
def decode( # type: ignore[override]
|
|
115
|
+
self,
|
|
116
|
+
trace: DigitalTrace | None = None,
|
|
117
|
+
*,
|
|
118
|
+
bck: NDArray[np.bool_] | None = None,
|
|
119
|
+
ws: NDArray[np.bool_] | None = None,
|
|
120
|
+
sd: NDArray[np.bool_] | None = None,
|
|
121
|
+
sample_rate: float = 1.0,
|
|
122
|
+
) -> Iterator[ProtocolPacket]:
|
|
123
|
+
"""Decode I2S audio data.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
trace: Optional primary trace.
|
|
127
|
+
bck: Bit Clock signal.
|
|
128
|
+
ws: Word Select signal (0=left, 1=right).
|
|
129
|
+
sd: Serial Data signal.
|
|
130
|
+
sample_rate: Sample rate in Hz.
|
|
131
|
+
|
|
132
|
+
Yields:
|
|
133
|
+
Decoded I2S samples as ProtocolPacket objects.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> decoder = I2SDecoder(bit_depth=16)
|
|
137
|
+
>>> for pkt in decoder.decode(bck=bck, ws=ws, sd=sd, sample_rate=1e6):
|
|
138
|
+
... print(f"Left: {pkt.annotations['left_sample']}")
|
|
139
|
+
"""
|
|
140
|
+
if bck is None or ws is None or sd is None:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
n_samples = min(len(bck), len(ws), len(sd))
|
|
144
|
+
bck = bck[:n_samples]
|
|
145
|
+
ws = ws[:n_samples]
|
|
146
|
+
sd = sd[:n_samples]
|
|
147
|
+
|
|
148
|
+
# Find rising edges of BCK (data sampled on rising edge in I2S)
|
|
149
|
+
rising_edges = np.where(~bck[:-1] & bck[1:])[0] + 1
|
|
150
|
+
|
|
151
|
+
# Find WS transitions to identify word boundaries
|
|
152
|
+
ws_transitions = np.where(ws[:-1] != ws[1:])[0] + 1
|
|
153
|
+
|
|
154
|
+
if len(rising_edges) == 0 or len(ws_transitions) == 0:
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
trans_num = 0
|
|
158
|
+
ws_idx = 0
|
|
159
|
+
|
|
160
|
+
while ws_idx < len(ws_transitions) - 1:
|
|
161
|
+
# Get word boundaries
|
|
162
|
+
word_start_idx = ws_transitions[ws_idx]
|
|
163
|
+
word_end_idx = ws_transitions[ws_idx + 1]
|
|
164
|
+
|
|
165
|
+
# Determine channel (WS=0 is left, WS=1 is right in standard I2S)
|
|
166
|
+
is_left = not ws[word_start_idx]
|
|
167
|
+
|
|
168
|
+
# Find BCK edges in this word period
|
|
169
|
+
word_edges = rising_edges[
|
|
170
|
+
(rising_edges >= word_start_idx) & (rising_edges < word_end_idx)
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
if len(word_edges) == 0:
|
|
174
|
+
ws_idx += 1
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# In standard I2S mode, data starts 1 clock after WS change
|
|
178
|
+
# In left-justified mode, data starts at WS change
|
|
179
|
+
# In right-justified mode, data is aligned to end of word period
|
|
180
|
+
if self._mode == I2SMode.STANDARD:
|
|
181
|
+
# Skip first edge (data starts on second edge)
|
|
182
|
+
data_edges = word_edges[1:] if len(word_edges) > 1 else []
|
|
183
|
+
elif self._mode == I2SMode.LEFT_JUSTIFIED:
|
|
184
|
+
data_edges = word_edges
|
|
185
|
+
else: # RIGHT_JUSTIFIED
|
|
186
|
+
# Take last bit_depth edges
|
|
187
|
+
data_edges = (
|
|
188
|
+
word_edges[-self._bit_depth :]
|
|
189
|
+
if len(word_edges) >= self._bit_depth
|
|
190
|
+
else word_edges
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Extract sample data (MSB first)
|
|
194
|
+
sample_bits = []
|
|
195
|
+
for edge_idx in data_edges[: self._bit_depth]:
|
|
196
|
+
if edge_idx < len(sd):
|
|
197
|
+
sample_bits.append(1 if sd[edge_idx] else 0)
|
|
198
|
+
|
|
199
|
+
if len(sample_bits) < self._bit_depth:
|
|
200
|
+
# Incomplete sample, pad with zeros
|
|
201
|
+
sample_bits.extend([0] * (self._bit_depth - len(sample_bits)))
|
|
202
|
+
|
|
203
|
+
# Convert to signed integer value (MSB first, two's complement)
|
|
204
|
+
sample_value = 0
|
|
205
|
+
for bit in sample_bits:
|
|
206
|
+
sample_value = (sample_value << 1) | bit
|
|
207
|
+
|
|
208
|
+
# Convert from unsigned to signed (two's complement)
|
|
209
|
+
if sample_bits[0] == 1: # Negative number
|
|
210
|
+
sample_value = sample_value - (1 << self._bit_depth)
|
|
211
|
+
|
|
212
|
+
# Calculate timing
|
|
213
|
+
start_time = word_start_idx / sample_rate
|
|
214
|
+
end_time = word_end_idx / sample_rate
|
|
215
|
+
|
|
216
|
+
# Store left and right channels
|
|
217
|
+
if ws_idx % 2 == 0:
|
|
218
|
+
# First word of stereo pair
|
|
219
|
+
left_sample = sample_value if is_left else 0
|
|
220
|
+
right_sample = 0 if is_left else sample_value
|
|
221
|
+
first_start_time = start_time
|
|
222
|
+
else:
|
|
223
|
+
# Second word of stereo pair - emit packet
|
|
224
|
+
if is_left:
|
|
225
|
+
left_sample = sample_value
|
|
226
|
+
else:
|
|
227
|
+
right_sample = sample_value
|
|
228
|
+
|
|
229
|
+
# Add annotation
|
|
230
|
+
self.put_annotation(
|
|
231
|
+
first_start_time,
|
|
232
|
+
end_time,
|
|
233
|
+
AnnotationLevel.PACKETS,
|
|
234
|
+
f"L: {left_sample} / R: {right_sample}",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Create packet
|
|
238
|
+
annotations = {
|
|
239
|
+
"sample_num": trans_num,
|
|
240
|
+
"left_sample": left_sample,
|
|
241
|
+
"right_sample": right_sample,
|
|
242
|
+
"bit_depth": self._bit_depth,
|
|
243
|
+
"mode": self._mode.value,
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
# Encode as bytes (little-endian, signed)
|
|
247
|
+
byte_count = (self._bit_depth + 7) // 8
|
|
248
|
+
left_bytes = left_sample.to_bytes(byte_count, "little", signed=True)
|
|
249
|
+
right_bytes = right_sample.to_bytes(byte_count, "little", signed=True)
|
|
250
|
+
data_bytes = left_bytes + right_bytes
|
|
251
|
+
|
|
252
|
+
packet = ProtocolPacket(
|
|
253
|
+
timestamp=first_start_time,
|
|
254
|
+
protocol="i2s",
|
|
255
|
+
data=data_bytes,
|
|
256
|
+
annotations=annotations,
|
|
257
|
+
errors=[],
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
yield packet
|
|
261
|
+
trans_num += 1
|
|
262
|
+
|
|
263
|
+
ws_idx += 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def decode_i2s(
|
|
267
|
+
bck: NDArray[np.bool_],
|
|
268
|
+
ws: NDArray[np.bool_],
|
|
269
|
+
sd: NDArray[np.bool_],
|
|
270
|
+
sample_rate: float = 1.0,
|
|
271
|
+
bit_depth: int = 16,
|
|
272
|
+
mode: Literal["standard", "left_justified", "right_justified"] = "standard",
|
|
273
|
+
) -> list[ProtocolPacket]:
|
|
274
|
+
"""Convenience function to decode I2S audio data.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
bck: Bit Clock signal.
|
|
278
|
+
ws: Word Select signal.
|
|
279
|
+
sd: Serial Data signal.
|
|
280
|
+
sample_rate: Sample rate in Hz.
|
|
281
|
+
bit_depth: Bits per sample (8, 16, 24, 32).
|
|
282
|
+
mode: Alignment mode.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
List of decoded I2S stereo samples.
|
|
286
|
+
|
|
287
|
+
Example:
|
|
288
|
+
>>> packets = decode_i2s(bck, ws, sd, sample_rate=1e6, bit_depth=16)
|
|
289
|
+
>>> for pkt in packets:
|
|
290
|
+
... print(f"L={pkt.annotations['left_sample']}, R={pkt.annotations['right_sample']}")
|
|
291
|
+
"""
|
|
292
|
+
decoder = I2SDecoder(bit_depth=bit_depth, mode=mode)
|
|
293
|
+
return list(decoder.decode(bck=bck, ws=ws, sd=sd, sample_rate=sample_rate))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
__all__ = ["I2SDecoder", "I2SMode", "decode_i2s"]
|