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,599 @@
|
|
|
1
|
+
"""Threshold configuration for voltage levels and logic families.
|
|
2
|
+
|
|
3
|
+
This module provides threshold configuration for digital signal analysis
|
|
4
|
+
including logic family definitions, threshold profiles, and per-analysis
|
|
5
|
+
overrides.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
import threading
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from oscura.config.schema import validate_against_schema
|
|
19
|
+
from oscura.core.exceptions import ConfigurationError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class LogicFamily:
|
|
26
|
+
"""Logic family voltage threshold definition.
|
|
27
|
+
|
|
28
|
+
Defines voltage thresholds per IEEE/JEDEC standards for digital
|
|
29
|
+
signal interpretation.
|
|
30
|
+
|
|
31
|
+
Attributes:
|
|
32
|
+
name: Logic family name (e.g., "TTL", "CMOS_3V3")
|
|
33
|
+
VIH: Input high voltage threshold (V)
|
|
34
|
+
VIL: Input low voltage threshold (V)
|
|
35
|
+
VOH: Output high voltage (V)
|
|
36
|
+
VOL: Output low voltage (V)
|
|
37
|
+
VCC: Supply voltage (V)
|
|
38
|
+
description: Human-readable description
|
|
39
|
+
temperature_range: Operating temperature range (min, max) in C
|
|
40
|
+
noise_margin_high: High state noise margin (V)
|
|
41
|
+
noise_margin_low: Low state noise margin (V)
|
|
42
|
+
source: Origin of definition (builtin, user, file path)
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> ttl = LogicFamily(
|
|
46
|
+
... name="TTL",
|
|
47
|
+
... VIH=2.0, VIL=0.8,
|
|
48
|
+
... VOH=2.4, VOL=0.4,
|
|
49
|
+
... VCC=5.0
|
|
50
|
+
... )
|
|
51
|
+
>>> print(f"TTL noise margin high: {ttl.noise_margin_high}V")
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
name: str
|
|
55
|
+
VIH: float # Input high threshold
|
|
56
|
+
VIL: float # Input low threshold
|
|
57
|
+
VOH: float # Output high level
|
|
58
|
+
VOL: float # Output low level
|
|
59
|
+
VCC: float = 5.0
|
|
60
|
+
description: str = ""
|
|
61
|
+
temperature_range: tuple[float, float] = field(default_factory=lambda: (0, 70))
|
|
62
|
+
noise_margin_high: float | None = None
|
|
63
|
+
noise_margin_low: float | None = None
|
|
64
|
+
source: str = "builtin"
|
|
65
|
+
|
|
66
|
+
def __post_init__(self) -> None:
|
|
67
|
+
"""Validate thresholds and compute noise margins."""
|
|
68
|
+
# Validate threshold ordering
|
|
69
|
+
if self.VIH <= self.VIL:
|
|
70
|
+
raise ConfigurationError(
|
|
71
|
+
f"Invalid thresholds for {self.name}: VIH ({self.VIH}V) must be > VIL ({self.VIL}V)"
|
|
72
|
+
)
|
|
73
|
+
if self.VOH <= self.VOL:
|
|
74
|
+
raise ConfigurationError(
|
|
75
|
+
f"Invalid thresholds for {self.name}: VOH ({self.VOH}V) must be > VOL ({self.VOL}V)"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Compute noise margins if not provided
|
|
79
|
+
if self.noise_margin_high is None:
|
|
80
|
+
self.noise_margin_high = self.VOH - self.VIH
|
|
81
|
+
if self.noise_margin_low is None:
|
|
82
|
+
self.noise_margin_low = self.VIL - self.VOL
|
|
83
|
+
|
|
84
|
+
def get_threshold(self, percent: float = 50.0) -> float:
|
|
85
|
+
"""Get threshold voltage at given percentage between VIL and VIH.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
percent: Percentage between VIL (0%) and VIH (100%)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Threshold voltage
|
|
92
|
+
"""
|
|
93
|
+
return self.VIL + (self.VIH - self.VIL) * (percent / 100.0)
|
|
94
|
+
|
|
95
|
+
def with_temperature_derating(
|
|
96
|
+
self, temperature: float, derating_factor: float = 0.002
|
|
97
|
+
) -> LogicFamily:
|
|
98
|
+
"""Create copy with temperature-derated thresholds.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
temperature: Operating temperature in Celsius
|
|
102
|
+
derating_factor: Derating factor per degree C (default 0.2%/C)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
New LogicFamily with adjusted thresholds
|
|
106
|
+
"""
|
|
107
|
+
# Simple linear derating from nominal 25C
|
|
108
|
+
delta_t = temperature - 25.0
|
|
109
|
+
factor = 1.0 - (delta_t * derating_factor)
|
|
110
|
+
|
|
111
|
+
return LogicFamily(
|
|
112
|
+
name=f"{self.name}@{temperature}C",
|
|
113
|
+
VIH=self.VIH * factor,
|
|
114
|
+
VIL=self.VIL * factor,
|
|
115
|
+
VOH=self.VOH * factor,
|
|
116
|
+
VOL=self.VOL * factor,
|
|
117
|
+
VCC=self.VCC,
|
|
118
|
+
description=f"{self.description} (derated for {temperature}C)",
|
|
119
|
+
temperature_range=self.temperature_range,
|
|
120
|
+
source=self.source,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class ThresholdProfile:
|
|
126
|
+
"""Named threshold profile combining logic family with adjustments.
|
|
127
|
+
|
|
128
|
+
Profiles allow users to save and reuse threshold configurations
|
|
129
|
+
for specific analysis scenarios.
|
|
130
|
+
|
|
131
|
+
Attributes:
|
|
132
|
+
name: Profile name
|
|
133
|
+
base_family: Base logic family name
|
|
134
|
+
overrides: Override values for specific thresholds
|
|
135
|
+
tolerance: Tolerance percentage (0-100)
|
|
136
|
+
description: Profile description
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> profile = ThresholdProfile(
|
|
140
|
+
... name="strict_ttl",
|
|
141
|
+
... base_family="TTL",
|
|
142
|
+
... overrides={"VIH": 2.2},
|
|
143
|
+
... tolerance=0
|
|
144
|
+
... )
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
name: str
|
|
148
|
+
base_family: str = "TTL"
|
|
149
|
+
overrides: dict[str, float] = field(default_factory=dict)
|
|
150
|
+
tolerance: float = 0.0 # 0-100%
|
|
151
|
+
description: str = ""
|
|
152
|
+
|
|
153
|
+
def apply_to(self, family: LogicFamily) -> LogicFamily:
|
|
154
|
+
"""Apply profile overrides to a logic family.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
family: Base logic family
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
New LogicFamily with overrides applied
|
|
161
|
+
"""
|
|
162
|
+
# Apply tolerance
|
|
163
|
+
factor = 1.0 + (self.tolerance / 100.0)
|
|
164
|
+
|
|
165
|
+
return LogicFamily(
|
|
166
|
+
name=f"{family.name}_{self.name}",
|
|
167
|
+
VIH=self.overrides.get("VIH", family.VIH),
|
|
168
|
+
VIL=self.overrides.get("VIL", family.VIL),
|
|
169
|
+
VOH=self.overrides.get("VOH", family.VOH * factor),
|
|
170
|
+
VOL=self.overrides.get("VOL", family.VOL / factor),
|
|
171
|
+
VCC=self.overrides.get("VCC", family.VCC),
|
|
172
|
+
description=f"{family.description} with {self.name} profile",
|
|
173
|
+
temperature_range=family.temperature_range,
|
|
174
|
+
source="profile",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ThresholdRegistry:
|
|
179
|
+
"""Registry for logic families and threshold profiles.
|
|
180
|
+
|
|
181
|
+
Manages built-in and user-defined logic families with support
|
|
182
|
+
for runtime overrides and profile switching.
|
|
183
|
+
|
|
184
|
+
Thread-safe singleton implementation with locks protecting shared state.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
>>> registry = ThresholdRegistry()
|
|
188
|
+
>>> ttl = registry.get_family("TTL")
|
|
189
|
+
>>> cmos = registry.get_family("CMOS_3V3")
|
|
190
|
+
>>> families = registry.list_families()
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
_instance: ThresholdRegistry | None = None
|
|
194
|
+
_lock: threading.Lock = threading.Lock()
|
|
195
|
+
|
|
196
|
+
# Instance attributes (initialized in __new__)
|
|
197
|
+
_families: dict[str, LogicFamily]
|
|
198
|
+
_profiles: dict[str, ThresholdProfile]
|
|
199
|
+
_session_overrides: dict[str, float]
|
|
200
|
+
_state_lock: threading.Lock
|
|
201
|
+
|
|
202
|
+
def __new__(cls) -> ThresholdRegistry:
|
|
203
|
+
"""Ensure singleton instance (thread-safe)."""
|
|
204
|
+
if cls._instance is None:
|
|
205
|
+
with cls._lock:
|
|
206
|
+
# Double-check locking pattern
|
|
207
|
+
if cls._instance is None:
|
|
208
|
+
cls._instance = super().__new__(cls)
|
|
209
|
+
cls._instance._families = {} # type: ignore[attr-defined]
|
|
210
|
+
cls._instance._profiles = {} # type: ignore[attr-defined]
|
|
211
|
+
cls._instance._session_overrides = {} # type: ignore[attr-defined]
|
|
212
|
+
cls._instance._state_lock = threading.Lock() # type: ignore[attr-defined]
|
|
213
|
+
cls._instance._register_builtins()
|
|
214
|
+
return cls._instance
|
|
215
|
+
|
|
216
|
+
def _register_builtins(self) -> None:
|
|
217
|
+
"""Register built-in logic family definitions."""
|
|
218
|
+
builtins = [
|
|
219
|
+
# TTL
|
|
220
|
+
LogicFamily(
|
|
221
|
+
name="TTL",
|
|
222
|
+
VIH=2.0,
|
|
223
|
+
VIL=0.8,
|
|
224
|
+
VOH=2.4,
|
|
225
|
+
VOL=0.4,
|
|
226
|
+
VCC=5.0,
|
|
227
|
+
description="Standard TTL (74xx series)",
|
|
228
|
+
),
|
|
229
|
+
# CMOS variants
|
|
230
|
+
LogicFamily(
|
|
231
|
+
name="CMOS_5V",
|
|
232
|
+
VIH=3.5,
|
|
233
|
+
VIL=1.5,
|
|
234
|
+
VOH=4.9,
|
|
235
|
+
VOL=0.1,
|
|
236
|
+
VCC=5.0,
|
|
237
|
+
description="CMOS 5V (74HCxx series)",
|
|
238
|
+
),
|
|
239
|
+
LogicFamily(
|
|
240
|
+
name="LVTTL_3V3",
|
|
241
|
+
VIH=2.0,
|
|
242
|
+
VIL=0.8,
|
|
243
|
+
VOH=2.4,
|
|
244
|
+
VOL=0.4,
|
|
245
|
+
VCC=3.3,
|
|
246
|
+
description="Low Voltage TTL 3.3V",
|
|
247
|
+
),
|
|
248
|
+
LogicFamily(
|
|
249
|
+
name="LVCMOS_3V3",
|
|
250
|
+
VIH=2.0,
|
|
251
|
+
VIL=0.7,
|
|
252
|
+
VOH=2.4,
|
|
253
|
+
VOL=0.4,
|
|
254
|
+
VCC=3.3,
|
|
255
|
+
description="Low Voltage CMOS 3.3V",
|
|
256
|
+
),
|
|
257
|
+
LogicFamily(
|
|
258
|
+
name="LVCMOS_2V5",
|
|
259
|
+
VIH=1.7,
|
|
260
|
+
VIL=0.7,
|
|
261
|
+
VOH=2.0,
|
|
262
|
+
VOL=0.4,
|
|
263
|
+
VCC=2.5,
|
|
264
|
+
description="Low Voltage CMOS 2.5V",
|
|
265
|
+
),
|
|
266
|
+
LogicFamily(
|
|
267
|
+
name="LVCMOS_1V8",
|
|
268
|
+
VIH=1.17,
|
|
269
|
+
VIL=0.63,
|
|
270
|
+
VOH=1.35,
|
|
271
|
+
VOL=0.45,
|
|
272
|
+
VCC=1.8,
|
|
273
|
+
description="Low Voltage CMOS 1.8V",
|
|
274
|
+
),
|
|
275
|
+
LogicFamily(
|
|
276
|
+
name="LVCMOS_1V5",
|
|
277
|
+
VIH=0.975,
|
|
278
|
+
VIL=0.525,
|
|
279
|
+
VOH=1.125,
|
|
280
|
+
VOL=0.375,
|
|
281
|
+
VCC=1.5,
|
|
282
|
+
description="Low Voltage CMOS 1.5V",
|
|
283
|
+
),
|
|
284
|
+
LogicFamily(
|
|
285
|
+
name="LVCMOS_1V2",
|
|
286
|
+
VIH=0.84, # 0.7 * 1.2
|
|
287
|
+
VIL=0.36, # 0.3 * 1.2
|
|
288
|
+
VOH=1.1,
|
|
289
|
+
VOL=0.1,
|
|
290
|
+
VCC=1.2,
|
|
291
|
+
description="Low Voltage CMOS 1.2V",
|
|
292
|
+
),
|
|
293
|
+
# ECL
|
|
294
|
+
LogicFamily(
|
|
295
|
+
name="ECL",
|
|
296
|
+
VIH=-0.9,
|
|
297
|
+
VIL=-1.7,
|
|
298
|
+
VOH=-0.9,
|
|
299
|
+
VOL=-1.75,
|
|
300
|
+
VCC=-5.2,
|
|
301
|
+
description="Emitter-Coupled Logic (ECL 10K)",
|
|
302
|
+
),
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
for family in builtins:
|
|
306
|
+
self._families[family.name] = family # type: ignore[attr-defined]
|
|
307
|
+
|
|
308
|
+
# Built-in profiles
|
|
309
|
+
builtin_profiles = [
|
|
310
|
+
ThresholdProfile(
|
|
311
|
+
name="strict",
|
|
312
|
+
base_family="TTL",
|
|
313
|
+
tolerance=0,
|
|
314
|
+
description="Exact specification values",
|
|
315
|
+
),
|
|
316
|
+
ThresholdProfile(
|
|
317
|
+
name="relaxed",
|
|
318
|
+
base_family="TTL",
|
|
319
|
+
tolerance=20,
|
|
320
|
+
description="20% tolerance for real-world signals",
|
|
321
|
+
),
|
|
322
|
+
ThresholdProfile(
|
|
323
|
+
name="auto",
|
|
324
|
+
base_family="TTL",
|
|
325
|
+
tolerance=10,
|
|
326
|
+
description="Auto-adjusted based on signal confidence",
|
|
327
|
+
),
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
for profile in builtin_profiles:
|
|
331
|
+
self._profiles[profile.name] = profile # type: ignore[attr-defined]
|
|
332
|
+
|
|
333
|
+
def get_family(self, name: str) -> LogicFamily:
|
|
334
|
+
"""Get logic family by name.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
name: Logic family name (case-insensitive)
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
Logic family definition
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
KeyError: If family not found
|
|
344
|
+
"""
|
|
345
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
346
|
+
# Try exact match first
|
|
347
|
+
if name in self._families: # type: ignore[attr-defined]
|
|
348
|
+
family = self._families[name] # type: ignore[attr-defined]
|
|
349
|
+
# Try case-insensitive match
|
|
350
|
+
elif name.upper() in self._families: # type: ignore[attr-defined]
|
|
351
|
+
family = self._families[name.upper()] # type: ignore[attr-defined]
|
|
352
|
+
else:
|
|
353
|
+
available = list(self._families.keys()) # type: ignore[attr-defined]
|
|
354
|
+
raise KeyError(f"Logic family '{name}' not found. Available: {available}")
|
|
355
|
+
|
|
356
|
+
# Apply session overrides
|
|
357
|
+
if self._session_overrides: # type: ignore[attr-defined]
|
|
358
|
+
return LogicFamily(
|
|
359
|
+
name=family.name,
|
|
360
|
+
VIH=self._session_overrides.get("VIH", family.VIH), # type: ignore[attr-defined]
|
|
361
|
+
VIL=self._session_overrides.get("VIL", family.VIL), # type: ignore[attr-defined]
|
|
362
|
+
VOH=self._session_overrides.get("VOH", family.VOH), # type: ignore[attr-defined]
|
|
363
|
+
VOL=self._session_overrides.get("VOL", family.VOL), # type: ignore[attr-defined]
|
|
364
|
+
VCC=self._session_overrides.get("VCC", family.VCC), # type: ignore[attr-defined]
|
|
365
|
+
description=family.description,
|
|
366
|
+
temperature_range=family.temperature_range,
|
|
367
|
+
source="override",
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return family # type: ignore[no-any-return]
|
|
371
|
+
|
|
372
|
+
def list_families(self) -> list[str]:
|
|
373
|
+
"""List all available logic families.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
List of family names
|
|
377
|
+
"""
|
|
378
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
379
|
+
return sorted(self._families.keys()) # type: ignore[attr-defined]
|
|
380
|
+
|
|
381
|
+
def register_family(self, family: LogicFamily, *, namespace: str = "user") -> None:
|
|
382
|
+
"""Register custom logic family.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
family: Logic family definition
|
|
386
|
+
namespace: Namespace prefix for custom families
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
>>> custom = LogicFamily(name="my_custom", VIH=2.5, VIL=1.0, VOH=3.0, VOL=0.5)
|
|
390
|
+
>>> registry.register_family(custom)
|
|
391
|
+
>>> # Available as "user.my_custom"
|
|
392
|
+
"""
|
|
393
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
394
|
+
# Namespace custom families
|
|
395
|
+
if namespace and not family.name.startswith(f"{namespace}."):
|
|
396
|
+
name = f"{namespace}.{family.name}"
|
|
397
|
+
else:
|
|
398
|
+
name = family.name
|
|
399
|
+
|
|
400
|
+
# Update family with new name
|
|
401
|
+
family = LogicFamily(
|
|
402
|
+
name=name,
|
|
403
|
+
VIH=family.VIH,
|
|
404
|
+
VIL=family.VIL,
|
|
405
|
+
VOH=family.VOH,
|
|
406
|
+
VOL=family.VOL,
|
|
407
|
+
VCC=family.VCC,
|
|
408
|
+
description=family.description,
|
|
409
|
+
temperature_range=family.temperature_range,
|
|
410
|
+
source=family.source,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
self._families[name] = family # type: ignore[attr-defined]
|
|
414
|
+
logger.info(f"Registered custom logic family: {name}")
|
|
415
|
+
|
|
416
|
+
def set_threshold_override(self, **kwargs: float) -> None:
|
|
417
|
+
"""Set session-level threshold overrides.
|
|
418
|
+
|
|
419
|
+
Overrides persist for session lifetime until reset.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
**kwargs: Threshold overrides (VIH, VIL, VOH, VOL, VCC)
|
|
423
|
+
|
|
424
|
+
Raises:
|
|
425
|
+
ValueError: If invalid threshold key or value out of range.
|
|
426
|
+
|
|
427
|
+
Example:
|
|
428
|
+
>>> registry.set_threshold_override(VIH=2.5, VIL=0.7)
|
|
429
|
+
"""
|
|
430
|
+
valid_keys = {"VIH", "VIL", "VOH", "VOL", "VCC"}
|
|
431
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
432
|
+
for key, value in kwargs.items():
|
|
433
|
+
if key not in valid_keys:
|
|
434
|
+
raise ValueError(f"Invalid threshold key: {key}. Valid: {valid_keys}")
|
|
435
|
+
if not 0.0 <= value <= 10.0:
|
|
436
|
+
raise ValueError(f"Threshold {key}={value}V out of range (0-10V)")
|
|
437
|
+
self._session_overrides[key] = value # type: ignore[attr-defined]
|
|
438
|
+
|
|
439
|
+
logger.info(f"Set threshold overrides: {kwargs}")
|
|
440
|
+
|
|
441
|
+
def reset_overrides(self) -> None:
|
|
442
|
+
"""Reset session threshold overrides."""
|
|
443
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
444
|
+
self._session_overrides.clear() # type: ignore[attr-defined]
|
|
445
|
+
logger.info("Reset threshold overrides")
|
|
446
|
+
|
|
447
|
+
def get_profile(self, name: str) -> ThresholdProfile:
|
|
448
|
+
"""Get threshold profile by name.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
name: Profile name
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Threshold profile
|
|
455
|
+
|
|
456
|
+
Raises:
|
|
457
|
+
KeyError: If profile not found.
|
|
458
|
+
"""
|
|
459
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
460
|
+
if name not in self._profiles: # type: ignore[attr-defined]
|
|
461
|
+
raise KeyError(
|
|
462
|
+
f"Profile '{name}' not found. Available: {list(self._profiles.keys())}"
|
|
463
|
+
) # type: ignore[attr-defined]
|
|
464
|
+
return self._profiles[name] # type: ignore[no-any-return, attr-defined]
|
|
465
|
+
|
|
466
|
+
def apply_profile(self, name: str) -> LogicFamily:
|
|
467
|
+
"""Apply a threshold profile.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
name: Profile name
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
Logic family with profile applied
|
|
474
|
+
"""
|
|
475
|
+
profile = self.get_profile(name)
|
|
476
|
+
base_family = self.get_family(profile.base_family)
|
|
477
|
+
return profile.apply_to(base_family)
|
|
478
|
+
|
|
479
|
+
def save_profile(
|
|
480
|
+
self, name: str, base_family: str | None = None, path: str | Path | None = None
|
|
481
|
+
) -> None:
|
|
482
|
+
"""Save current settings as named profile.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
name: Profile name
|
|
486
|
+
base_family: Base family name (default: "TTL")
|
|
487
|
+
path: Optional file path to save
|
|
488
|
+
|
|
489
|
+
Example:
|
|
490
|
+
>>> registry.set_threshold_override(VIH=2.5)
|
|
491
|
+
>>> registry.save_profile("my_profile")
|
|
492
|
+
"""
|
|
493
|
+
with self._state_lock: # type: ignore[attr-defined]
|
|
494
|
+
profile = ThresholdProfile(
|
|
495
|
+
name=name,
|
|
496
|
+
base_family=base_family or "TTL",
|
|
497
|
+
overrides=dict(self._session_overrides), # type: ignore[attr-defined]
|
|
498
|
+
description=f"User profile {name}",
|
|
499
|
+
)
|
|
500
|
+
self._profiles[name] = profile # type: ignore[attr-defined]
|
|
501
|
+
|
|
502
|
+
if path:
|
|
503
|
+
path = Path(path)
|
|
504
|
+
data = {
|
|
505
|
+
"name": profile.name,
|
|
506
|
+
"base_family": profile.base_family,
|
|
507
|
+
"overrides": profile.overrides,
|
|
508
|
+
"tolerance": profile.tolerance,
|
|
509
|
+
"description": profile.description,
|
|
510
|
+
}
|
|
511
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
512
|
+
yaml.dump(data, f)
|
|
513
|
+
logger.info(f"Saved profile to {path}")
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def load_logic_family(path: str | Path) -> LogicFamily:
|
|
517
|
+
"""Load logic family from YAML/JSON file.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
path: Path to file
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Loaded logic family
|
|
524
|
+
"""
|
|
525
|
+
path = Path(path)
|
|
526
|
+
|
|
527
|
+
with open(path, encoding="utf-8") as f:
|
|
528
|
+
data = yaml.safe_load(f)
|
|
529
|
+
|
|
530
|
+
# Validate against schema
|
|
531
|
+
validate_against_schema(data, "logic_family")
|
|
532
|
+
|
|
533
|
+
return LogicFamily(
|
|
534
|
+
name=data["name"],
|
|
535
|
+
VIH=data["VIH"],
|
|
536
|
+
VIL=data["VIL"],
|
|
537
|
+
VOH=data["VOH"],
|
|
538
|
+
VOL=data["VOL"],
|
|
539
|
+
VCC=data.get("VCC", 5.0),
|
|
540
|
+
description=data.get("description", ""),
|
|
541
|
+
temperature_range=tuple(data.get("temperature_range", {}).values()) or (0, 70),
|
|
542
|
+
noise_margin_high=data.get("noise_margin_high"),
|
|
543
|
+
noise_margin_low=data.get("noise_margin_low"),
|
|
544
|
+
source=str(path),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def get_threshold_registry() -> ThresholdRegistry:
|
|
549
|
+
"""Get the global threshold registry.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
Global ThresholdRegistry instance
|
|
553
|
+
"""
|
|
554
|
+
return ThresholdRegistry()
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def get_user_logic_families_dir() -> Path:
|
|
558
|
+
"""Get user directory for custom logic families.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Path to ~/.oscura/logic_families/
|
|
562
|
+
"""
|
|
563
|
+
home = Path.home()
|
|
564
|
+
xdg_config = os.environ.get("XDG_CONFIG_HOME")
|
|
565
|
+
base = Path(xdg_config) if xdg_config else home / ".config"
|
|
566
|
+
|
|
567
|
+
dir_path = base / "oscura" / "logic_families"
|
|
568
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
569
|
+
return dir_path
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def load_user_logic_families() -> list[LogicFamily]:
|
|
573
|
+
"""Load all user-defined logic families.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
List of loaded logic families
|
|
577
|
+
"""
|
|
578
|
+
families = []
|
|
579
|
+
user_dir = get_user_logic_families_dir()
|
|
580
|
+
|
|
581
|
+
for file_path in user_dir.glob("*.yaml"):
|
|
582
|
+
try:
|
|
583
|
+
family = load_logic_family(file_path)
|
|
584
|
+
families.append(family)
|
|
585
|
+
except Exception as e:
|
|
586
|
+
logger.warning(f"Failed to load logic family from {file_path}: {e}")
|
|
587
|
+
|
|
588
|
+
return families
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
__all__ = [
|
|
592
|
+
"LogicFamily",
|
|
593
|
+
"ThresholdProfile",
|
|
594
|
+
"ThresholdRegistry",
|
|
595
|
+
"get_threshold_registry",
|
|
596
|
+
"get_user_logic_families_dir",
|
|
597
|
+
"load_logic_family",
|
|
598
|
+
"load_user_logic_families",
|
|
599
|
+
]
|