SCPI-Instrument-Control 1.0.1__tar.gz → 1.1.0__tar.gz
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.
- {scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info → scpi_instrument_control-1.1.0}/PKG-INFO +1 -1
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0/SCPI_Instrument_Control.egg-info}/PKG-INFO +1 -1
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/SOURCES.txt +15 -1
- scpi_instrument_control-1.1.0/examples/data_logger_basic.py +147 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/pyproject.toml +1 -1
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/__init__.py +10 -1
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/mock.py +33 -1
- scpi_instrument_control-1.1.0/scpi_control/daq_models.py +316 -0
- scpi_instrument_control-1.1.0/scpi_control/daq_scpi_commands.py +227 -0
- scpi_instrument_control-1.1.0/scpi_control/data_logger.py +762 -0
- scpi_instrument_control-1.1.0/scpi_control/gui/daq_worker.py +205 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/main_window.py +119 -1
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/__init__.py +14 -1
- scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_ai_panel.py +392 -0
- scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_channel_config.py +317 -0
- scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_data_view.py +373 -0
- scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_scan_config.py +279 -0
- scpi_instrument_control-1.1.0/scpi_control/gui/widgets/data_logger_control.py +364 -0
- scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/__init__.py +20 -0
- scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_analyzer.py +316 -0
- scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_context_builder.py +340 -0
- scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_prompts.py +114 -0
- scpi_instrument_control-1.1.0/siglent/exceptions.py +40 -0
- scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/__init__.py +0 -7
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/CHANGELOG.md +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/LICENSE +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/MANIFEST.in +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/README.md +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/dependency_links.txt +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/entry_points.txt +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/requires.txt +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/top_level.txt +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/SDS800XHD_Series_ProgrammingGuide_EN11G.pdf +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/VISUAL_MEASUREMENTS.md +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/01_main_window.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/channel_controls.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/cursors.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/fft_analysis.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/main_window.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/measurements_panel.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/visual_measurements.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/README.md +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/advanced_analysis.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/basic_usage.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/batch_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/continuous_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/function_generator_basic.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/live_plot.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/measurements.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/probe_calibration_analysis.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_advanced_features.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_basic_control.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_gui_test.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_usb_connection.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/report_generation_example.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/simple_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/trigger_based_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/vector_graphics_xy_mode.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/waveform_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment Wide.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.icns +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.ico +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.png +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/analysis.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/automation.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_models.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_output.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_scpi_commands.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/channel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/base.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/socket.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/visa_connection.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/exceptions.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/function_generator.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/app.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/connection_manager.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/live_view_worker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/utils/validators.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/vnc_window.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/waveform_capture_worker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/channel_control.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/cursor_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/error_dialog.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/fft_display.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/math_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_marker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/frequency_marker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/timing_marker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/voltage_marker.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/protocol_decode_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/psu_control.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/reference_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/scope_web_view.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/terminal_widget.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/timebase_control.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/trigger_control.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/vector_graphics_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/visual_measurement_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/waveform_display.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/waveform_display_pg.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/math_channel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/measurement.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/measurement_config.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/models.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/oscilloscope.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/power_supply.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/power_supply_output.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decode.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/i2c_decoder.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/spi_decoder.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/uart_decoder.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_data_logger.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_models.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_scpi_commands.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/py.typed +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/reference_waveform.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/app.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/base.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/markdown_generator.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/pdf_generator.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/analyzer.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/client.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/context_builder.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/prompts.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/main_window.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/app_settings.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/criteria.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/plot_style.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/report_data.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/report_options.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/template.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/test_types.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/image_handler.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/waveform_analyzer.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/waveform_loader.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/__init__.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/ai_analysis_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/chat_sidebar.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/llm_settings_dialog.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/metadata_panel.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/pdf_preview_dialog.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/report_options_dialog.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/template_manager_dialog.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/scpi_commands.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/screen_capture.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/trigger.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/vector_graphics.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/waveform.py +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/setup.cfg +0 -0
- {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/siglent/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SCPI-Instrument-Control
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Universal Python library for controlling SCPI-compatible test equipment via Ethernet/LAN. Supports oscilloscopes (Siglent SDS series, generic SCPI scopes), function generators/AWGs (Siglent SDG series, generic SCPI AWGs), and power supplies (Siglent SPD series, generic SCPI PSUs). Features include waveform capture, measurements, FFT analysis, PyQt6 GUI, automated report generation with AI analysis, and comprehensive SCPI command abstraction.
|
|
5
5
|
Author: Robin Mumm
|
|
6
6
|
Maintainer: little-did-I-know
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: SCPI-Instrument-Control
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Universal Python library for controlling SCPI-compatible test equipment via Ethernet/LAN. Supports oscilloscopes (Siglent SDS series, generic SCPI scopes), function generators/AWGs (Siglent SDG series, generic SCPI AWGs), and power supplies (Siglent SPD series, generic SCPI PSUs). Features include waveform capture, measurements, FFT analysis, PyQt6 GUI, automated report generation with AI analysis, and comprehensive SCPI command abstraction.
|
|
5
5
|
Author: Robin Mumm
|
|
6
6
|
Maintainer: little-did-I-know
|
|
@@ -23,6 +23,7 @@ examples/advanced_analysis.py
|
|
|
23
23
|
examples/basic_usage.py
|
|
24
24
|
examples/batch_capture.py
|
|
25
25
|
examples/continuous_capture.py
|
|
26
|
+
examples/data_logger_basic.py
|
|
26
27
|
examples/function_generator_basic.py
|
|
27
28
|
examples/live_plot.py
|
|
28
29
|
examples/measurements.py
|
|
@@ -47,6 +48,9 @@ scpi_control/awg_models.py
|
|
|
47
48
|
scpi_control/awg_output.py
|
|
48
49
|
scpi_control/awg_scpi_commands.py
|
|
49
50
|
scpi_control/channel.py
|
|
51
|
+
scpi_control/daq_models.py
|
|
52
|
+
scpi_control/daq_scpi_commands.py
|
|
53
|
+
scpi_control/data_logger.py
|
|
50
54
|
scpi_control/exceptions.py
|
|
51
55
|
scpi_control/function_generator.py
|
|
52
56
|
scpi_control/math_channel.py
|
|
@@ -75,6 +79,7 @@ scpi_control/connection/visa_connection.py
|
|
|
75
79
|
scpi_control/gui/__init__.py
|
|
76
80
|
scpi_control/gui/app.py
|
|
77
81
|
scpi_control/gui/connection_manager.py
|
|
82
|
+
scpi_control/gui/daq_worker.py
|
|
78
83
|
scpi_control/gui/live_view_worker.py
|
|
79
84
|
scpi_control/gui/main_window.py
|
|
80
85
|
scpi_control/gui/vnc_window.py
|
|
@@ -83,6 +88,11 @@ scpi_control/gui/utils/validators.py
|
|
|
83
88
|
scpi_control/gui/widgets/__init__.py
|
|
84
89
|
scpi_control/gui/widgets/channel_control.py
|
|
85
90
|
scpi_control/gui/widgets/cursor_panel.py
|
|
91
|
+
scpi_control/gui/widgets/daq_ai_panel.py
|
|
92
|
+
scpi_control/gui/widgets/daq_channel_config.py
|
|
93
|
+
scpi_control/gui/widgets/daq_data_view.py
|
|
94
|
+
scpi_control/gui/widgets/daq_scan_config.py
|
|
95
|
+
scpi_control/gui/widgets/data_logger_control.py
|
|
86
96
|
scpi_control/gui/widgets/error_dialog.py
|
|
87
97
|
scpi_control/gui/widgets/fft_display.py
|
|
88
98
|
scpi_control/gui/widgets/math_panel.py
|
|
@@ -118,6 +128,9 @@ scpi_control/report_generator/llm/__init__.py
|
|
|
118
128
|
scpi_control/report_generator/llm/analyzer.py
|
|
119
129
|
scpi_control/report_generator/llm/client.py
|
|
120
130
|
scpi_control/report_generator/llm/context_builder.py
|
|
131
|
+
scpi_control/report_generator/llm/daq_analyzer.py
|
|
132
|
+
scpi_control/report_generator/llm/daq_context_builder.py
|
|
133
|
+
scpi_control/report_generator/llm/daq_prompts.py
|
|
121
134
|
scpi_control/report_generator/llm/prompts.py
|
|
122
135
|
scpi_control/report_generator/models/__init__.py
|
|
123
136
|
scpi_control/report_generator/models/app_settings.py
|
|
@@ -139,4 +152,5 @@ scpi_control/report_generator/widgets/metadata_panel.py
|
|
|
139
152
|
scpi_control/report_generator/widgets/pdf_preview_dialog.py
|
|
140
153
|
scpi_control/report_generator/widgets/report_options_dialog.py
|
|
141
154
|
scpi_control/report_generator/widgets/template_manager_dialog.py
|
|
142
|
-
siglent/__init__.py
|
|
155
|
+
siglent/__init__.py
|
|
156
|
+
siglent/exceptions.py
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Basic Data Logger / DAQ example.
|
|
2
|
+
|
|
3
|
+
This example demonstrates basic usage of the DataLogger class for
|
|
4
|
+
data acquisition systems like the Keysight 34970A/DAQ970A.
|
|
5
|
+
|
|
6
|
+
Requirements:
|
|
7
|
+
- A SCPI-compatible DAQ system (Keysight 34970A, DAQ970A, or similar)
|
|
8
|
+
- Network connection to the instrument
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from scpi_control import DataLogger
|
|
12
|
+
|
|
13
|
+
# Replace with your DAQ's IP address
|
|
14
|
+
DAQ_IP = "192.168.1.100"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def basic_measurements():
|
|
18
|
+
"""Demonstrate basic voltage measurements."""
|
|
19
|
+
print("=== Basic Voltage Measurements ===\n")
|
|
20
|
+
|
|
21
|
+
with DataLogger(DAQ_IP) as daq:
|
|
22
|
+
print(f"Connected to: {daq.identify()}")
|
|
23
|
+
print(f"Model: {daq.model_capability.model_name}")
|
|
24
|
+
print(f"Channels available: {daq.model_capability.get_all_channels()}\n")
|
|
25
|
+
|
|
26
|
+
# Configure channels 101-103 for DC voltage measurement
|
|
27
|
+
channels = [101, 102, 103]
|
|
28
|
+
daq.configure_voltage_dc(channels, range="AUTO", resolution="AUTO")
|
|
29
|
+
print(f"Configured channels {channels} for DC voltage\n")
|
|
30
|
+
|
|
31
|
+
# Take immediate measurements
|
|
32
|
+
readings = daq.measure_voltage_dc(channels)
|
|
33
|
+
print("Measurement results:")
|
|
34
|
+
for reading in readings:
|
|
35
|
+
print(f" Channel {reading.channel}: {reading.value:.6f} {reading.unit}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def scan_multiple_channels():
|
|
39
|
+
"""Demonstrate scanning multiple channels."""
|
|
40
|
+
print("\n=== Multi-Channel Scan ===\n")
|
|
41
|
+
|
|
42
|
+
with DataLogger(DAQ_IP) as daq:
|
|
43
|
+
# Configure different measurement types on different channels
|
|
44
|
+
daq.configure_voltage_dc([101, 102], range="10") # 10V range
|
|
45
|
+
daq.configure_temperature([103, 104], sensor_type="TC", sensor_subtype="K")
|
|
46
|
+
daq.configure_resistance([105], four_wire=False)
|
|
47
|
+
|
|
48
|
+
# Set up scan list
|
|
49
|
+
scan_channels = [101, 102, 103, 104, 105]
|
|
50
|
+
daq.set_scan_list(scan_channels)
|
|
51
|
+
print(f"Scan list: {daq.get_scan_list()}")
|
|
52
|
+
|
|
53
|
+
# Configure trigger for immediate single scan
|
|
54
|
+
daq.set_trigger_source("IMM")
|
|
55
|
+
daq.set_trigger_count(1)
|
|
56
|
+
|
|
57
|
+
# Initiate and read
|
|
58
|
+
readings = daq.read()
|
|
59
|
+
print(f"\nScanned {len(readings)} channels:")
|
|
60
|
+
for i, reading in enumerate(readings):
|
|
61
|
+
ch = scan_channels[i] if i < len(scan_channels) else "?"
|
|
62
|
+
print(f" Channel {ch}: {reading.value:.6f}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def continuous_logging():
|
|
66
|
+
"""Demonstrate continuous data logging."""
|
|
67
|
+
print("\n=== Continuous Logging (5 seconds) ===\n")
|
|
68
|
+
|
|
69
|
+
with DataLogger(DAQ_IP) as daq:
|
|
70
|
+
# Configure for voltage monitoring
|
|
71
|
+
channels = [101, 102]
|
|
72
|
+
daq.configure_voltage_dc(channels)
|
|
73
|
+
|
|
74
|
+
# Log at 1 second intervals for 5 seconds
|
|
75
|
+
print("Logging for 5 seconds...")
|
|
76
|
+
all_readings = daq.start_logging(
|
|
77
|
+
channels=channels,
|
|
78
|
+
interval=1.0,
|
|
79
|
+
duration=5.0,
|
|
80
|
+
callback=lambda r: print(f" Got {len(r)} readings"),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
print(f"\nTotal scans collected: {len(all_readings)}")
|
|
84
|
+
print(f"Total readings: {sum(len(r) for r in all_readings)}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def alarm_monitoring():
|
|
88
|
+
"""Demonstrate alarm/limit checking."""
|
|
89
|
+
print("\n=== Alarm Monitoring ===\n")
|
|
90
|
+
|
|
91
|
+
with DataLogger(DAQ_IP) as daq:
|
|
92
|
+
if not daq.model_capability.has_alarm:
|
|
93
|
+
print("This model does not support alarm limits")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Configure voltage measurement with limits
|
|
97
|
+
channel = 101
|
|
98
|
+
daq.configure_voltage_dc(channel)
|
|
99
|
+
|
|
100
|
+
# Set alarm limits: warn if voltage goes outside 0-5V
|
|
101
|
+
daq.set_alarm_limits(channel, high=5.0, low=0.0)
|
|
102
|
+
daq.enable_alarm(channel, enable=True)
|
|
103
|
+
print(f"Alarm limits set on channel {channel}: 0V to 5V")
|
|
104
|
+
|
|
105
|
+
# Take a measurement
|
|
106
|
+
readings = daq.measure_voltage_dc(channel)
|
|
107
|
+
print(f"Current reading: {readings[0].value:.3f} V")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def scaling_example():
|
|
111
|
+
"""Demonstrate mx+b scaling."""
|
|
112
|
+
print("\n=== Scaling (mx+b) Example ===\n")
|
|
113
|
+
|
|
114
|
+
with DataLogger(DAQ_IP) as daq:
|
|
115
|
+
if not daq.model_capability.has_math:
|
|
116
|
+
print("This model does not support scaling")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
channel = 101
|
|
120
|
+
daq.configure_voltage_dc(channel)
|
|
121
|
+
|
|
122
|
+
# Apply scaling: convert 0-10V input to 0-100% display
|
|
123
|
+
# Scaled value = (reading * 10) + 0 = percentage
|
|
124
|
+
daq.set_scaling(channel, gain=10.0, offset=0.0, enable=True)
|
|
125
|
+
print("Scaling configured: 0-10V input -> 0-100% output")
|
|
126
|
+
|
|
127
|
+
readings = daq.measure_voltage_dc(channel)
|
|
128
|
+
print(f"Raw voltage: {readings[0].value:.3f} V")
|
|
129
|
+
# Note: scaled value would be returned if DAQ returns scaled data
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
print("Data Logger / DAQ Basic Examples")
|
|
134
|
+
print("================================\n")
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
basic_measurements()
|
|
138
|
+
# scan_multiple_channels()
|
|
139
|
+
# continuous_logging()
|
|
140
|
+
# alarm_monitoring()
|
|
141
|
+
# scaling_example()
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f"Error: {e}")
|
|
144
|
+
print("\nMake sure:")
|
|
145
|
+
print(f" 1. Your DAQ is connected at {DAQ_IP}")
|
|
146
|
+
print(" 2. The IP address is correct")
|
|
147
|
+
print(" 3. No other software is using the connection")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "SCPI-Instrument-Control"
|
|
7
|
-
version = "1.0
|
|
7
|
+
version = "1.1.0"
|
|
8
8
|
description = "Universal Python library for controlling SCPI-compatible test equipment via Ethernet/LAN. Supports oscilloscopes (Siglent SDS series, generic SCPI scopes), function generators/AWGs (Siglent SDG series, generic SCPI AWGs), and power supplies (Siglent SPD series, generic SCPI PSUs). Features include waveform capture, measurements, FFT analysis, PyQt6 GUI, automated report generation with AI analysis, and comprehensive SCPI command abstraction."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -7,6 +7,7 @@ Supported equipment:
|
|
|
7
7
|
- Oscilloscopes (Siglent SDS series and SCPI-compatible models)
|
|
8
8
|
- Function Generators / AWGs (Siglent SDG series and SCPI-compatible models)
|
|
9
9
|
- Power Supplies (Siglent SPD series and SCPI-compatible models)
|
|
10
|
+
- Data Acquisition / Data Loggers (Keysight 34970A/DAQ970A and SCPI-compatible models)
|
|
10
11
|
|
|
11
12
|
For high-level automation and data collection, see the automation module:
|
|
12
13
|
from scpi_control.automation import DataCollector, TriggerWaitCollector
|
|
@@ -17,11 +18,14 @@ For power supply control:
|
|
|
17
18
|
For function generator control:
|
|
18
19
|
from scpi_control import FunctionGenerator
|
|
19
20
|
|
|
21
|
+
For data acquisition / data logging:
|
|
22
|
+
from scpi_control import DataLogger
|
|
23
|
+
|
|
20
24
|
For automated test report generation:
|
|
21
25
|
from scpi_control.report_generator import ReportGenerator
|
|
22
26
|
"""
|
|
23
27
|
|
|
24
|
-
__version__ = "1.0
|
|
28
|
+
__version__ = "1.1.0"
|
|
25
29
|
|
|
26
30
|
from scpi_control.exceptions import CommandError, SiglentConnectionError, SiglentError, SiglentTimeoutError
|
|
27
31
|
from scpi_control.oscilloscope import Oscilloscope
|
|
@@ -33,6 +37,9 @@ from scpi_control.psu_data_logger import PSUDataLogger, TimedPSULogger
|
|
|
33
37
|
# Function generator support (new in v0.6.0, stable in v1.0.0)
|
|
34
38
|
from scpi_control.function_generator import FunctionGenerator
|
|
35
39
|
|
|
40
|
+
# Data acquisition / Data logger support (new in v1.1.0)
|
|
41
|
+
from scpi_control.data_logger import DataLogger
|
|
42
|
+
|
|
36
43
|
__all__ = [
|
|
37
44
|
# Core features
|
|
38
45
|
"Oscilloscope",
|
|
@@ -46,4 +53,6 @@ __all__ = [
|
|
|
46
53
|
"TimedPSULogger",
|
|
47
54
|
# Function generator features
|
|
48
55
|
"FunctionGenerator",
|
|
56
|
+
# Data acquisition features
|
|
57
|
+
"DataLogger",
|
|
49
58
|
]
|
{scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/mock.py
RENAMED
|
@@ -45,6 +45,10 @@ class MockConnection(BaseConnection):
|
|
|
45
45
|
awg_mode: bool = False,
|
|
46
46
|
awg_idn: str = "Siglent Technologies,SDG1032X,SDG1XXXXX,2.01.01.37R1",
|
|
47
47
|
awg_channels: Optional[Dict[int, Dict[str, any]]] = None,
|
|
48
|
+
# Data acquisition (DAQ) parameters
|
|
49
|
+
daq_mode: bool = False,
|
|
50
|
+
daq_idn: str = "Keysight Technologies,34970A,MY12345678,A.01.02",
|
|
51
|
+
daq_readings: str = "1.234,2.345,3.456",
|
|
48
52
|
):
|
|
49
53
|
super().__init__(host, port, timeout)
|
|
50
54
|
channels = channel_states.keys() if channel_states else range(1, 3)
|
|
@@ -112,6 +116,12 @@ class MockConnection(BaseConnection):
|
|
|
112
116
|
},
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
# Data acquisition (DAQ) mode
|
|
120
|
+
self.daq_mode = daq_mode
|
|
121
|
+
self.daq_idn = daq_idn
|
|
122
|
+
self.daq_readings = daq_readings
|
|
123
|
+
self.daq_scan_list = []
|
|
124
|
+
|
|
115
125
|
def connect(self) -> None:
|
|
116
126
|
"""Mark the connection as established."""
|
|
117
127
|
self._connected = True
|
|
@@ -365,13 +375,35 @@ class MockConnection(BaseConnection):
|
|
|
365
375
|
upper = command.upper()
|
|
366
376
|
|
|
367
377
|
if upper == "*IDN?":
|
|
368
|
-
if self.
|
|
378
|
+
if self.daq_mode:
|
|
379
|
+
return self.daq_idn
|
|
380
|
+
elif self.awg_mode:
|
|
369
381
|
return self.awg_idn
|
|
370
382
|
elif self.psu_mode:
|
|
371
383
|
return self.psu_idn
|
|
372
384
|
else:
|
|
373
385
|
return self.idn
|
|
374
386
|
|
|
387
|
+
# Data acquisition (DAQ) queries
|
|
388
|
+
if self.daq_mode:
|
|
389
|
+
# Return configured readings for any measurement/read/fetch query
|
|
390
|
+
if any(kw in upper for kw in ["READ?", "FETC?", "MEAS:", "R?"]):
|
|
391
|
+
return self.daq_readings
|
|
392
|
+
|
|
393
|
+
# Scan list query
|
|
394
|
+
if "ROUT:SCAN?" in upper:
|
|
395
|
+
if self.daq_scan_list:
|
|
396
|
+
return f"(@{','.join(str(ch) for ch in self.daq_scan_list)})"
|
|
397
|
+
return "(@)"
|
|
398
|
+
|
|
399
|
+
# Data points query
|
|
400
|
+
if "DATA:POIN?" in upper:
|
|
401
|
+
return str(len(self.daq_readings.split(",")))
|
|
402
|
+
|
|
403
|
+
# Error query
|
|
404
|
+
if "SYST:ERR?" in upper:
|
|
405
|
+
return '+0,"No error"'
|
|
406
|
+
|
|
375
407
|
# Function generator (AWG) queries
|
|
376
408
|
if self.awg_mode:
|
|
377
409
|
# Function queries: C1:BSWV? WVTP (Siglent) or SOUR1:FUNC? (generic)
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""Model capability definitions for SCPI-controlled Data Acquisition systems.
|
|
2
|
+
|
|
3
|
+
Supports generic SCPI-99 DAQ/Data Loggers and Keysight 34970A/DAQ970A series models.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MeasurementFunction(Enum):
|
|
16
|
+
"""Supported measurement functions for DAQ channels."""
|
|
17
|
+
|
|
18
|
+
VOLTAGE_DC = "VOLT:DC"
|
|
19
|
+
VOLTAGE_AC = "VOLT:AC"
|
|
20
|
+
CURRENT_DC = "CURR:DC"
|
|
21
|
+
CURRENT_AC = "CURR:AC"
|
|
22
|
+
RESISTANCE_2W = "RES" # 2-wire resistance
|
|
23
|
+
RESISTANCE_4W = "FRES" # 4-wire (Fresistance)
|
|
24
|
+
FREQUENCY = "FREQ"
|
|
25
|
+
PERIOD = "PER"
|
|
26
|
+
TEMPERATURE = "TEMP" # Thermocouple, RTD, thermistor
|
|
27
|
+
CONTINUITY = "CONT"
|
|
28
|
+
DIODE = "DIOD"
|
|
29
|
+
DIGITAL_INPUT = "DIG:INP"
|
|
30
|
+
DIGITAL_OUTPUT = "DIG:OUTP"
|
|
31
|
+
TOTALIZER = "TOT" # Counter/totalizer
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ModuleSpec:
|
|
36
|
+
"""Specification for a DAQ plug-in module/card."""
|
|
37
|
+
|
|
38
|
+
slot: int # Slot number (1, 2, 3, etc.)
|
|
39
|
+
module_type: str # Module type identifier (e.g., "34901A", "DAQM901A")
|
|
40
|
+
num_channels: int # Number of channels on this module
|
|
41
|
+
channel_start: int # Starting channel number (e.g., 101 for slot 1)
|
|
42
|
+
supported_functions: List[MeasurementFunction] # Supported measurement types
|
|
43
|
+
max_voltage: float = 300.0 # Maximum input voltage
|
|
44
|
+
max_current: float = 1.0 # Maximum input current (if applicable)
|
|
45
|
+
has_switching: bool = True # Multiplexer/switching capability
|
|
46
|
+
description: str = "" # Human-readable description
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
"""String representation of module spec."""
|
|
50
|
+
return f"Slot {self.slot}: {self.module_type} ({self.num_channels}ch, {self.channel_start}-{self.channel_start + self.num_channels - 1})"
|
|
51
|
+
|
|
52
|
+
def get_channel_list(self) -> List[int]:
|
|
53
|
+
"""Get list of all channel numbers for this module."""
|
|
54
|
+
return list(range(self.channel_start, self.channel_start + self.num_channels))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class DAQCapability:
|
|
59
|
+
"""Defines capabilities and features for a specific DAQ model.
|
|
60
|
+
|
|
61
|
+
This dataclass contains all model-specific information including hardware
|
|
62
|
+
specifications and supported features.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
model_name: str # Full model name (e.g., "34970A", "DAQ970A")
|
|
66
|
+
manufacturer: str # Manufacturer name (e.g., "Keysight", "Agilent")
|
|
67
|
+
num_slots: int # Number of module slots
|
|
68
|
+
modules: List[ModuleSpec] = field(default_factory=list) # Installed modules
|
|
69
|
+
has_internal_dmm: bool = True # Built-in digital multimeter
|
|
70
|
+
dmm_resolution: float = 6.5 # DMM resolution in digits
|
|
71
|
+
max_sample_rate: float = 250.0 # Maximum scan rate (channels/second)
|
|
72
|
+
memory_readings: int = 50000 # Reading memory capacity
|
|
73
|
+
has_timestamp: bool = True # Timestamp capability
|
|
74
|
+
has_alarm: bool = True # Alarm/limit checking
|
|
75
|
+
has_math: bool = True # Math functions (mx+b scaling)
|
|
76
|
+
scpi_variant: str = "generic" # SCPI command variant
|
|
77
|
+
|
|
78
|
+
def __str__(self) -> str:
|
|
79
|
+
"""String representation of DAQ capability."""
|
|
80
|
+
return f"{self.manufacturer} {self.model_name} ({self.num_slots} slots, {self.scpi_variant})"
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def total_channels(self) -> int:
|
|
84
|
+
"""Get total number of channels across all modules."""
|
|
85
|
+
return sum(m.num_channels for m in self.modules)
|
|
86
|
+
|
|
87
|
+
def get_all_channels(self) -> List[int]:
|
|
88
|
+
"""Get list of all channel numbers across all modules."""
|
|
89
|
+
channels = []
|
|
90
|
+
for module in self.modules:
|
|
91
|
+
channels.extend(module.get_channel_list())
|
|
92
|
+
return sorted(channels)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Common module types
|
|
96
|
+
MULTIPLEXER_20CH = lambda slot: ModuleSpec(
|
|
97
|
+
slot=slot,
|
|
98
|
+
module_type="34901A",
|
|
99
|
+
num_channels=20,
|
|
100
|
+
channel_start=slot * 100 + 1,
|
|
101
|
+
supported_functions=[
|
|
102
|
+
MeasurementFunction.VOLTAGE_DC,
|
|
103
|
+
MeasurementFunction.VOLTAGE_AC,
|
|
104
|
+
MeasurementFunction.CURRENT_DC,
|
|
105
|
+
MeasurementFunction.CURRENT_AC,
|
|
106
|
+
MeasurementFunction.RESISTANCE_2W,
|
|
107
|
+
MeasurementFunction.RESISTANCE_4W,
|
|
108
|
+
MeasurementFunction.FREQUENCY,
|
|
109
|
+
MeasurementFunction.PERIOD,
|
|
110
|
+
MeasurementFunction.TEMPERATURE,
|
|
111
|
+
],
|
|
112
|
+
max_voltage=300.0,
|
|
113
|
+
description="20-Channel Multiplexer (2/4-wire)",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
MULTIPLEXER_40CH = lambda slot: ModuleSpec(
|
|
117
|
+
slot=slot,
|
|
118
|
+
module_type="34902A",
|
|
119
|
+
num_channels=16,
|
|
120
|
+
channel_start=slot * 100 + 1,
|
|
121
|
+
supported_functions=[
|
|
122
|
+
MeasurementFunction.VOLTAGE_DC,
|
|
123
|
+
MeasurementFunction.VOLTAGE_AC,
|
|
124
|
+
MeasurementFunction.RESISTANCE_2W,
|
|
125
|
+
MeasurementFunction.FREQUENCY,
|
|
126
|
+
MeasurementFunction.PERIOD,
|
|
127
|
+
MeasurementFunction.TEMPERATURE,
|
|
128
|
+
],
|
|
129
|
+
max_voltage=300.0,
|
|
130
|
+
description="16-Channel Multiplexer (2-wire only)",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
DIGITAL_IO_MODULE = lambda slot: ModuleSpec(
|
|
134
|
+
slot=slot,
|
|
135
|
+
module_type="34903A",
|
|
136
|
+
num_channels=20,
|
|
137
|
+
channel_start=slot * 100 + 1,
|
|
138
|
+
supported_functions=[
|
|
139
|
+
MeasurementFunction.DIGITAL_INPUT,
|
|
140
|
+
MeasurementFunction.DIGITAL_OUTPUT,
|
|
141
|
+
],
|
|
142
|
+
has_switching=True,
|
|
143
|
+
description="20-Channel Actuator/GP Switch",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# DAQ Model Registry - Add new models here
|
|
148
|
+
DAQ_MODEL_REGISTRY = {
|
|
149
|
+
# Keysight/Agilent 34970A (classic model)
|
|
150
|
+
"34970A": DAQCapability(
|
|
151
|
+
model_name="34970A",
|
|
152
|
+
manufacturer="Keysight",
|
|
153
|
+
num_slots=3,
|
|
154
|
+
modules=[MULTIPLEXER_20CH(1)], # Default config - can be updated after connection
|
|
155
|
+
has_internal_dmm=True,
|
|
156
|
+
dmm_resolution=6.5,
|
|
157
|
+
max_sample_rate=250.0,
|
|
158
|
+
memory_readings=50000,
|
|
159
|
+
has_timestamp=True,
|
|
160
|
+
has_alarm=True,
|
|
161
|
+
has_math=True,
|
|
162
|
+
scpi_variant="keysight_daq",
|
|
163
|
+
),
|
|
164
|
+
# Keysight 34972A (USB enabled version)
|
|
165
|
+
"34972A": DAQCapability(
|
|
166
|
+
model_name="34972A",
|
|
167
|
+
manufacturer="Keysight",
|
|
168
|
+
num_slots=3,
|
|
169
|
+
modules=[MULTIPLEXER_20CH(1)],
|
|
170
|
+
has_internal_dmm=True,
|
|
171
|
+
dmm_resolution=6.5,
|
|
172
|
+
max_sample_rate=250.0,
|
|
173
|
+
memory_readings=50000,
|
|
174
|
+
has_timestamp=True,
|
|
175
|
+
has_alarm=True,
|
|
176
|
+
has_math=True,
|
|
177
|
+
scpi_variant="keysight_daq",
|
|
178
|
+
),
|
|
179
|
+
# Keysight DAQ970A (modern replacement)
|
|
180
|
+
"DAQ970A": DAQCapability(
|
|
181
|
+
model_name="DAQ970A",
|
|
182
|
+
manufacturer="Keysight",
|
|
183
|
+
num_slots=3,
|
|
184
|
+
modules=[MULTIPLEXER_20CH(1)],
|
|
185
|
+
has_internal_dmm=True,
|
|
186
|
+
dmm_resolution=6.5,
|
|
187
|
+
max_sample_rate=450.0, # Faster scanning
|
|
188
|
+
memory_readings=500000, # More memory
|
|
189
|
+
has_timestamp=True,
|
|
190
|
+
has_alarm=True,
|
|
191
|
+
has_math=True,
|
|
192
|
+
scpi_variant="keysight_daq",
|
|
193
|
+
),
|
|
194
|
+
# Keysight DAQ973A (no internal DMM)
|
|
195
|
+
"DAQ973A": DAQCapability(
|
|
196
|
+
model_name="DAQ973A",
|
|
197
|
+
manufacturer="Keysight",
|
|
198
|
+
num_slots=3,
|
|
199
|
+
modules=[],
|
|
200
|
+
has_internal_dmm=False,
|
|
201
|
+
dmm_resolution=0,
|
|
202
|
+
max_sample_rate=450.0,
|
|
203
|
+
memory_readings=500000,
|
|
204
|
+
has_timestamp=True,
|
|
205
|
+
has_alarm=True,
|
|
206
|
+
has_math=False,
|
|
207
|
+
scpi_variant="keysight_daq",
|
|
208
|
+
),
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def detect_daq_from_idn(idn_string: str) -> DAQCapability:
|
|
213
|
+
"""Detect DAQ model and return its capability profile.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
idn_string: The response from *IDN? command
|
|
217
|
+
Format: "Manufacturer,Model,Serial,Firmware"
|
|
218
|
+
Example: "Keysight Technologies,34970A,MY12345678,A.01.02"
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
DAQCapability object for the detected model
|
|
222
|
+
|
|
223
|
+
Note:
|
|
224
|
+
Unknown models will receive a generic SCPI capability profile
|
|
225
|
+
to enable basic control functionality.
|
|
226
|
+
"""
|
|
227
|
+
parts = idn_string.split(",")
|
|
228
|
+
if len(parts) < 2:
|
|
229
|
+
logger.warning(f"Invalid *IDN? response format: {idn_string}")
|
|
230
|
+
return create_generic_daq_capability(idn_string)
|
|
231
|
+
|
|
232
|
+
manufacturer = parts[0].strip()
|
|
233
|
+
model_from_idn = parts[1].strip()
|
|
234
|
+
logger.info(f"Detecting DAQ model from IDN: {manufacturer}, {model_from_idn}")
|
|
235
|
+
|
|
236
|
+
# Try exact match first
|
|
237
|
+
if model_from_idn in DAQ_MODEL_REGISTRY:
|
|
238
|
+
logger.info(f"Exact match found: {model_from_idn}")
|
|
239
|
+
return DAQ_MODEL_REGISTRY[model_from_idn]
|
|
240
|
+
|
|
241
|
+
# Try fuzzy matching
|
|
242
|
+
normalized_model = re.sub(r"[\s\-_]", "", model_from_idn).upper()
|
|
243
|
+
|
|
244
|
+
for registered_model, capability in DAQ_MODEL_REGISTRY.items():
|
|
245
|
+
normalized_registered = re.sub(r"[\s\-_]", "", registered_model).upper()
|
|
246
|
+
if normalized_model == normalized_registered:
|
|
247
|
+
logger.info(f"Fuzzy match found: {model_from_idn} -> {registered_model}")
|
|
248
|
+
return capability
|
|
249
|
+
|
|
250
|
+
# Try partial matching for Keysight/Agilent models
|
|
251
|
+
if any(mfr in manufacturer for mfr in ["Keysight", "Agilent", "HP"]):
|
|
252
|
+
for registered_model, capability in DAQ_MODEL_REGISTRY.items():
|
|
253
|
+
if registered_model in model_from_idn:
|
|
254
|
+
logger.info(f"Partial match found: {model_from_idn} -> {registered_model}")
|
|
255
|
+
return capability
|
|
256
|
+
|
|
257
|
+
# Model not found - create generic fallback
|
|
258
|
+
logger.warning(f"Model '{model_from_idn}' not in registry, using generic SCPI profile")
|
|
259
|
+
return create_generic_daq_capability(idn_string)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def create_generic_daq_capability(idn_string: str) -> DAQCapability:
|
|
263
|
+
"""Create generic SCPI capability for unknown DAQ.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
idn_string: The *IDN? response string
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Generic DAQCapability with conservative defaults
|
|
270
|
+
"""
|
|
271
|
+
parts = idn_string.split(",")
|
|
272
|
+
manufacturer = parts[0].strip() if len(parts) > 0 else "Unknown"
|
|
273
|
+
model = parts[1].strip() if len(parts) > 1 else "Generic DAQ"
|
|
274
|
+
|
|
275
|
+
logger.info(f"Creating generic DAQ capability for {manufacturer} {model}")
|
|
276
|
+
|
|
277
|
+
generic_capability = DAQCapability(
|
|
278
|
+
model_name=model,
|
|
279
|
+
manufacturer=manufacturer,
|
|
280
|
+
num_slots=1,
|
|
281
|
+
modules=[
|
|
282
|
+
ModuleSpec(
|
|
283
|
+
slot=1,
|
|
284
|
+
module_type="Generic",
|
|
285
|
+
num_channels=20,
|
|
286
|
+
channel_start=101,
|
|
287
|
+
supported_functions=[
|
|
288
|
+
MeasurementFunction.VOLTAGE_DC,
|
|
289
|
+
MeasurementFunction.VOLTAGE_AC,
|
|
290
|
+
MeasurementFunction.RESISTANCE_2W,
|
|
291
|
+
],
|
|
292
|
+
description="Generic Input Module",
|
|
293
|
+
)
|
|
294
|
+
],
|
|
295
|
+
has_internal_dmm=True,
|
|
296
|
+
dmm_resolution=5.5,
|
|
297
|
+
max_sample_rate=100.0,
|
|
298
|
+
memory_readings=10000,
|
|
299
|
+
has_timestamp=True,
|
|
300
|
+
has_alarm=False,
|
|
301
|
+
has_math=False,
|
|
302
|
+
scpi_variant="generic",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
logger.info(f"Created generic capability: {generic_capability}")
|
|
306
|
+
return generic_capability
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def list_supported_models() -> List[str]:
|
|
310
|
+
"""Get list of all explicitly supported DAQ model names."""
|
|
311
|
+
return sorted(DAQ_MODEL_REGISTRY.keys())
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def get_models_by_manufacturer(manufacturer: str) -> List[DAQCapability]:
|
|
315
|
+
"""Get all models from a specific manufacturer."""
|
|
316
|
+
return [cap for cap in DAQ_MODEL_REGISTRY.values() if cap.manufacturer.lower() == manufacturer.lower()]
|