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.
Files changed (159) hide show
  1. {scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info → scpi_instrument_control-1.1.0}/PKG-INFO +1 -1
  2. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0/SCPI_Instrument_Control.egg-info}/PKG-INFO +1 -1
  3. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/SOURCES.txt +15 -1
  4. scpi_instrument_control-1.1.0/examples/data_logger_basic.py +147 -0
  5. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/pyproject.toml +1 -1
  6. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/__init__.py +10 -1
  7. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/mock.py +33 -1
  8. scpi_instrument_control-1.1.0/scpi_control/daq_models.py +316 -0
  9. scpi_instrument_control-1.1.0/scpi_control/daq_scpi_commands.py +227 -0
  10. scpi_instrument_control-1.1.0/scpi_control/data_logger.py +762 -0
  11. scpi_instrument_control-1.1.0/scpi_control/gui/daq_worker.py +205 -0
  12. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/main_window.py +119 -1
  13. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/__init__.py +14 -1
  14. scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_ai_panel.py +392 -0
  15. scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_channel_config.py +317 -0
  16. scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_data_view.py +373 -0
  17. scpi_instrument_control-1.1.0/scpi_control/gui/widgets/daq_scan_config.py +279 -0
  18. scpi_instrument_control-1.1.0/scpi_control/gui/widgets/data_logger_control.py +364 -0
  19. scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/__init__.py +20 -0
  20. scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_analyzer.py +316 -0
  21. scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_context_builder.py +340 -0
  22. scpi_instrument_control-1.1.0/scpi_control/report_generator/llm/daq_prompts.py +114 -0
  23. scpi_instrument_control-1.1.0/siglent/exceptions.py +40 -0
  24. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/__init__.py +0 -7
  25. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/CHANGELOG.md +0 -0
  26. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/LICENSE +0 -0
  27. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/MANIFEST.in +0 -0
  28. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/README.md +0 -0
  29. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/dependency_links.txt +0 -0
  30. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/entry_points.txt +0 -0
  31. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/requires.txt +0 -0
  32. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/SCPI_Instrument_Control.egg-info/top_level.txt +0 -0
  33. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/SDS800XHD_Series_ProgrammingGuide_EN11G.pdf +0 -0
  34. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/VISUAL_MEASUREMENTS.md +0 -0
  35. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/01_main_window.png +0 -0
  36. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/channel_controls.png +0 -0
  37. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/cursors.png +0 -0
  38. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/fft_analysis.png +0 -0
  39. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/main_window.png +0 -0
  40. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/measurements_panel.png +0 -0
  41. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/docs/images/visual_measurements.png +0 -0
  42. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/README.md +0 -0
  43. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/advanced_analysis.py +0 -0
  44. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/basic_usage.py +0 -0
  45. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/batch_capture.py +0 -0
  46. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/continuous_capture.py +0 -0
  47. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/function_generator_basic.py +0 -0
  48. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/live_plot.py +0 -0
  49. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/measurements.py +0 -0
  50. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/probe_calibration_analysis.py +0 -0
  51. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_advanced_features.py +0 -0
  52. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_basic_control.py +0 -0
  53. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_gui_test.py +0 -0
  54. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/psu_usb_connection.py +0 -0
  55. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/report_generation_example.py +0 -0
  56. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/simple_capture.py +0 -0
  57. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/trigger_based_capture.py +0 -0
  58. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/vector_graphics_xy_mode.py +0 -0
  59. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/examples/waveform_capture.py +0 -0
  60. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment Wide.png +0 -0
  61. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.icns +0 -0
  62. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.ico +0 -0
  63. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/resources/Test Equipment.png +0 -0
  64. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/analysis.py +0 -0
  65. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/automation.py +0 -0
  66. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_models.py +0 -0
  67. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_output.py +0 -0
  68. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/awg_scpi_commands.py +0 -0
  69. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/channel.py +0 -0
  70. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/__init__.py +0 -0
  71. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/base.py +0 -0
  72. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/socket.py +0 -0
  73. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/connection/visa_connection.py +0 -0
  74. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/exceptions.py +0 -0
  75. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/function_generator.py +0 -0
  76. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/__init__.py +0 -0
  77. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/app.py +0 -0
  78. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/connection_manager.py +0 -0
  79. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/live_view_worker.py +0 -0
  80. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/utils/validators.py +0 -0
  81. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/vnc_window.py +0 -0
  82. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/waveform_capture_worker.py +0 -0
  83. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/channel_control.py +0 -0
  84. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/cursor_panel.py +0 -0
  85. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/error_dialog.py +0 -0
  86. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/fft_display.py +0 -0
  87. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/math_panel.py +0 -0
  88. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_marker.py +0 -0
  89. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/__init__.py +0 -0
  90. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/frequency_marker.py +0 -0
  91. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/timing_marker.py +0 -0
  92. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_markers/voltage_marker.py +0 -0
  93. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/measurement_panel.py +0 -0
  94. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/protocol_decode_panel.py +0 -0
  95. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/psu_control.py +0 -0
  96. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/reference_panel.py +0 -0
  97. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/scope_web_view.py +0 -0
  98. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/terminal_widget.py +0 -0
  99. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/timebase_control.py +0 -0
  100. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/trigger_control.py +0 -0
  101. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/vector_graphics_panel.py +0 -0
  102. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/visual_measurement_panel.py +0 -0
  103. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/waveform_display.py +0 -0
  104. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/gui/widgets/waveform_display_pg.py +0 -0
  105. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/math_channel.py +0 -0
  106. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/measurement.py +0 -0
  107. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/measurement_config.py +0 -0
  108. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/models.py +0 -0
  109. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/oscilloscope.py +0 -0
  110. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/power_supply.py +0 -0
  111. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/power_supply_output.py +0 -0
  112. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decode.py +0 -0
  113. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/__init__.py +0 -0
  114. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/i2c_decoder.py +0 -0
  115. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/spi_decoder.py +0 -0
  116. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/protocol_decoders/uart_decoder.py +0 -0
  117. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_data_logger.py +0 -0
  118. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_models.py +0 -0
  119. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/psu_scpi_commands.py +0 -0
  120. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/py.typed +0 -0
  121. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/reference_waveform.py +0 -0
  122. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/__init__.py +0 -0
  123. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/app.py +0 -0
  124. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/__init__.py +0 -0
  125. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/base.py +0 -0
  126. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/markdown_generator.py +0 -0
  127. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/generators/pdf_generator.py +0 -0
  128. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/analyzer.py +0 -0
  129. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/client.py +0 -0
  130. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/context_builder.py +0 -0
  131. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/llm/prompts.py +0 -0
  132. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/main_window.py +0 -0
  133. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/__init__.py +0 -0
  134. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/app_settings.py +0 -0
  135. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/criteria.py +0 -0
  136. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/plot_style.py +0 -0
  137. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/report_data.py +0 -0
  138. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/report_options.py +0 -0
  139. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/template.py +0 -0
  140. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/models/test_types.py +0 -0
  141. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/__init__.py +0 -0
  142. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/image_handler.py +0 -0
  143. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/waveform_analyzer.py +0 -0
  144. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/utils/waveform_loader.py +0 -0
  145. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/__init__.py +0 -0
  146. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/ai_analysis_panel.py +0 -0
  147. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/chat_sidebar.py +0 -0
  148. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/llm_settings_dialog.py +0 -0
  149. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/metadata_panel.py +0 -0
  150. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/pdf_preview_dialog.py +0 -0
  151. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/report_options_dialog.py +0 -0
  152. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/report_generator/widgets/template_manager_dialog.py +0 -0
  153. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/scpi_commands.py +0 -0
  154. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/screen_capture.py +0 -0
  155. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/trigger.py +0 -0
  156. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/vector_graphics.py +0 -0
  157. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/scpi_control/waveform.py +0 -0
  158. {scpi_instrument_control-1.0.1 → scpi_instrument_control-1.1.0}/setup.cfg +0 -0
  159. {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.1
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.1
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.1"
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.1"
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
  ]
@@ -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.awg_mode:
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()]