SCPI-Instrument-Control 1.0.0__tar.gz → 1.0.1__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 (147) hide show
  1. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/CHANGELOG.md +9 -0
  2. {scpi_instrument_control-1.0.0/SCPI_Instrument_Control.egg-info → scpi_instrument_control-1.0.1}/PKG-INFO +1 -1
  3. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info}/PKG-INFO +1 -1
  4. scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info/SOURCES.txt +142 -0
  5. scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info/entry_points.txt +3 -0
  6. scpi_instrument_control-1.0.1/SCPI_Instrument_Control.egg-info/top_level.txt +2 -0
  7. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/pyproject.toml +6 -5
  8. scpi_instrument_control-1.0.1/scpi_control/__init__.py +49 -0
  9. scpi_instrument_control-1.0.1/scpi_control/analysis.py +401 -0
  10. scpi_instrument_control-1.0.1/scpi_control/automation.py +557 -0
  11. scpi_instrument_control-1.0.1/scpi_control/awg_models.py +277 -0
  12. scpi_instrument_control-1.0.1/scpi_control/awg_output.py +503 -0
  13. scpi_instrument_control-1.0.1/scpi_control/awg_scpi_commands.py +183 -0
  14. scpi_instrument_control-1.0.1/scpi_control/channel.py +269 -0
  15. scpi_instrument_control-1.0.1/scpi_control/connection/__init__.py +40 -0
  16. scpi_instrument_control-1.0.1/scpi_control/connection/base.py +115 -0
  17. scpi_instrument_control-1.0.1/scpi_control/connection/mock.py +613 -0
  18. scpi_instrument_control-1.0.1/scpi_control/connection/socket.py +211 -0
  19. scpi_instrument_control-1.0.1/scpi_control/connection/visa_connection.py +395 -0
  20. scpi_instrument_control-1.0.1/scpi_control/exceptions.py +37 -0
  21. scpi_instrument_control-1.0.1/scpi_control/function_generator.py +381 -0
  22. scpi_instrument_control-1.0.1/scpi_control/gui/__init__.py +3 -0
  23. scpi_instrument_control-1.0.1/scpi_control/gui/app.py +118 -0
  24. scpi_instrument_control-1.0.1/scpi_control/gui/connection_manager.py +204 -0
  25. scpi_instrument_control-1.0.1/scpi_control/gui/live_view_worker.py +179 -0
  26. scpi_instrument_control-1.0.1/scpi_control/gui/main_window.py +1605 -0
  27. scpi_instrument_control-1.0.1/scpi_control/gui/utils/validators.py +177 -0
  28. scpi_instrument_control-1.0.1/scpi_control/gui/vnc_window.py +251 -0
  29. scpi_instrument_control-1.0.1/scpi_control/gui/waveform_capture_worker.py +132 -0
  30. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/__init__.py +21 -0
  31. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/channel_control.py +387 -0
  32. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/cursor_panel.py +319 -0
  33. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/error_dialog.py +222 -0
  34. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/fft_display.py +353 -0
  35. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/math_panel.py +327 -0
  36. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_marker.py +460 -0
  37. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_markers/__init__.py +32 -0
  38. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_markers/frequency_marker.py +251 -0
  39. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_markers/timing_marker.py +411 -0
  40. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_markers/voltage_marker.py +271 -0
  41. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/measurement_panel.py +320 -0
  42. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/protocol_decode_panel.py +417 -0
  43. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/psu_control.py +323 -0
  44. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/reference_panel.py +345 -0
  45. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/scope_web_view.py +160 -0
  46. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/terminal_widget.py +311 -0
  47. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/timebase_control.py +294 -0
  48. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/trigger_control.py +297 -0
  49. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/vector_graphics_panel.py +530 -0
  50. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/visual_measurement_panel.py +617 -0
  51. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/waveform_display.py +1120 -0
  52. scpi_instrument_control-1.0.1/scpi_control/gui/widgets/waveform_display_pg.py +713 -0
  53. scpi_instrument_control-1.0.1/scpi_control/math_channel.py +449 -0
  54. scpi_instrument_control-1.0.1/scpi_control/measurement.py +350 -0
  55. scpi_instrument_control-1.0.1/scpi_control/measurement_config.py +245 -0
  56. scpi_instrument_control-1.0.1/scpi_control/models.py +300 -0
  57. scpi_instrument_control-1.0.1/scpi_control/oscilloscope.py +461 -0
  58. scpi_instrument_control-1.0.1/scpi_control/power_supply.py +424 -0
  59. scpi_instrument_control-1.0.1/scpi_control/power_supply_output.py +410 -0
  60. scpi_instrument_control-1.0.1/scpi_control/protocol_decode.py +210 -0
  61. scpi_instrument_control-1.0.1/scpi_control/protocol_decoders/__init__.py +7 -0
  62. scpi_instrument_control-1.0.1/scpi_control/protocol_decoders/i2c_decoder.py +325 -0
  63. scpi_instrument_control-1.0.1/scpi_control/protocol_decoders/spi_decoder.py +264 -0
  64. scpi_instrument_control-1.0.1/scpi_control/protocol_decoders/uart_decoder.py +225 -0
  65. scpi_instrument_control-1.0.1/scpi_control/psu_data_logger.py +293 -0
  66. scpi_instrument_control-1.0.1/scpi_control/psu_models.py +236 -0
  67. scpi_instrument_control-1.0.1/scpi_control/psu_scpi_commands.py +162 -0
  68. scpi_instrument_control-1.0.1/scpi_control/py.typed +0 -0
  69. scpi_instrument_control-1.0.1/scpi_control/reference_waveform.py +412 -0
  70. scpi_instrument_control-1.0.1/scpi_control/report_generator/__init__.py +23 -0
  71. scpi_instrument_control-1.0.1/scpi_control/report_generator/app.py +38 -0
  72. scpi_instrument_control-1.0.1/scpi_control/report_generator/generators/__init__.py +12 -0
  73. scpi_instrument_control-1.0.1/scpi_control/report_generator/generators/base.py +53 -0
  74. scpi_instrument_control-1.0.1/scpi_control/report_generator/generators/markdown_generator.py +546 -0
  75. scpi_instrument_control-1.0.1/scpi_control/report_generator/generators/pdf_generator.py +1205 -0
  76. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/__init__.py +7 -0
  77. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/analyzer.py +287 -0
  78. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/client.py +446 -0
  79. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/context_builder.py +292 -0
  80. scpi_instrument_control-1.0.1/scpi_control/report_generator/llm/prompts.py +108 -0
  81. scpi_instrument_control-1.0.1/scpi_control/report_generator/main_window.py +862 -0
  82. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/__init__.py +16 -0
  83. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/app_settings.py +85 -0
  84. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/criteria.py +219 -0
  85. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/plot_style.py +82 -0
  86. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/report_data.py +549 -0
  87. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/report_options.py +63 -0
  88. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/template.py +493 -0
  89. scpi_instrument_control-1.0.1/scpi_control/report_generator/models/test_types.py +284 -0
  90. scpi_instrument_control-1.0.1/scpi_control/report_generator/utils/__init__.py +7 -0
  91. scpi_instrument_control-1.0.1/scpi_control/report_generator/utils/image_handler.py +194 -0
  92. scpi_instrument_control-1.0.1/scpi_control/report_generator/utils/waveform_analyzer.py +1138 -0
  93. scpi_instrument_control-1.0.1/scpi_control/report_generator/utils/waveform_loader.py +316 -0
  94. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/__init__.py +7 -0
  95. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/ai_analysis_panel.py +567 -0
  96. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/chat_sidebar.py +325 -0
  97. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/llm_settings_dialog.py +585 -0
  98. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/metadata_panel.py +299 -0
  99. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/pdf_preview_dialog.py +356 -0
  100. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/report_options_dialog.py +557 -0
  101. scpi_instrument_control-1.0.1/scpi_control/report_generator/widgets/template_manager_dialog.py +390 -0
  102. scpi_instrument_control-1.0.1/scpi_control/scpi_commands.py +206 -0
  103. scpi_instrument_control-1.0.1/scpi_control/screen_capture.py +261 -0
  104. scpi_instrument_control-1.0.1/scpi_control/trigger.py +325 -0
  105. scpi_instrument_control-1.0.1/scpi_control/vector_graphics.py +537 -0
  106. scpi_instrument_control-1.0.1/scpi_control/waveform.py +634 -0
  107. scpi_instrument_control-1.0.0/SCPI_Instrument_Control.egg-info/SOURCES.txt +0 -43
  108. scpi_instrument_control-1.0.0/SCPI_Instrument_Control.egg-info/entry_points.txt +0 -3
  109. scpi_instrument_control-1.0.0/SCPI_Instrument_Control.egg-info/top_level.txt +0 -1
  110. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/LICENSE +0 -0
  111. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/MANIFEST.in +0 -0
  112. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/README.md +0 -0
  113. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/SCPI_Instrument_Control.egg-info/dependency_links.txt +0 -0
  114. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/SCPI_Instrument_Control.egg-info/requires.txt +0 -0
  115. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/SDS800XHD_Series_ProgrammingGuide_EN11G.pdf +0 -0
  116. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/VISUAL_MEASUREMENTS.md +0 -0
  117. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/01_main_window.png +0 -0
  118. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/channel_controls.png +0 -0
  119. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/cursors.png +0 -0
  120. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/fft_analysis.png +0 -0
  121. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/main_window.png +0 -0
  122. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/measurements_panel.png +0 -0
  123. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/docs/images/visual_measurements.png +0 -0
  124. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/README.md +0 -0
  125. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/advanced_analysis.py +0 -0
  126. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/basic_usage.py +0 -0
  127. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/batch_capture.py +0 -0
  128. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/continuous_capture.py +0 -0
  129. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/function_generator_basic.py +0 -0
  130. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/live_plot.py +0 -0
  131. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/measurements.py +0 -0
  132. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/probe_calibration_analysis.py +0 -0
  133. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/psu_advanced_features.py +0 -0
  134. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/psu_basic_control.py +0 -0
  135. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/psu_gui_test.py +0 -0
  136. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/psu_usb_connection.py +0 -0
  137. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/report_generation_example.py +0 -0
  138. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/simple_capture.py +0 -0
  139. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/trigger_based_capture.py +0 -0
  140. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/vector_graphics_xy_mode.py +0 -0
  141. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/examples/waveform_capture.py +0 -0
  142. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/resources/Test Equipment Wide.png +0 -0
  143. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/resources/Test Equipment.icns +0 -0
  144. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/resources/Test Equipment.ico +0 -0
  145. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/resources/Test Equipment.png +0 -0
  146. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/setup.cfg +0 -0
  147. {scpi_instrument_control-1.0.0 → scpi_instrument_control-1.0.1}/siglent/__init__.py +0 -0
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+
11
+ ## [1.0.1] - 2026-01-07
12
+
13
+ ### Added
14
+
15
+ ### Changed
16
+
17
+ ### Fixed
18
+
10
19
  ## [1.0.0] - 2026-01-06
11
20
 
12
21
  ### ⚠️ MAJOR RELEASE - Package Renamed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: SCPI-Instrument-Control
3
- Version: 1.0.0
3
+ Version: 1.0.1
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.0
3
+ Version: 1.0.1
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
@@ -0,0 +1,142 @@
1
+ CHANGELOG.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ pyproject.toml
6
+ SCPI_Instrument_Control.egg-info/PKG-INFO
7
+ SCPI_Instrument_Control.egg-info/SOURCES.txt
8
+ SCPI_Instrument_Control.egg-info/dependency_links.txt
9
+ SCPI_Instrument_Control.egg-info/entry_points.txt
10
+ SCPI_Instrument_Control.egg-info/requires.txt
11
+ SCPI_Instrument_Control.egg-info/top_level.txt
12
+ docs/SDS800XHD_Series_ProgrammingGuide_EN11G.pdf
13
+ docs/VISUAL_MEASUREMENTS.md
14
+ docs/images/01_main_window.png
15
+ docs/images/channel_controls.png
16
+ docs/images/cursors.png
17
+ docs/images/fft_analysis.png
18
+ docs/images/main_window.png
19
+ docs/images/measurements_panel.png
20
+ docs/images/visual_measurements.png
21
+ examples/README.md
22
+ examples/advanced_analysis.py
23
+ examples/basic_usage.py
24
+ examples/batch_capture.py
25
+ examples/continuous_capture.py
26
+ examples/function_generator_basic.py
27
+ examples/live_plot.py
28
+ examples/measurements.py
29
+ examples/probe_calibration_analysis.py
30
+ examples/psu_advanced_features.py
31
+ examples/psu_basic_control.py
32
+ examples/psu_gui_test.py
33
+ examples/psu_usb_connection.py
34
+ examples/report_generation_example.py
35
+ examples/simple_capture.py
36
+ examples/trigger_based_capture.py
37
+ examples/vector_graphics_xy_mode.py
38
+ examples/waveform_capture.py
39
+ resources/Test Equipment Wide.png
40
+ resources/Test Equipment.icns
41
+ resources/Test Equipment.ico
42
+ resources/Test Equipment.png
43
+ scpi_control/__init__.py
44
+ scpi_control/analysis.py
45
+ scpi_control/automation.py
46
+ scpi_control/awg_models.py
47
+ scpi_control/awg_output.py
48
+ scpi_control/awg_scpi_commands.py
49
+ scpi_control/channel.py
50
+ scpi_control/exceptions.py
51
+ scpi_control/function_generator.py
52
+ scpi_control/math_channel.py
53
+ scpi_control/measurement.py
54
+ scpi_control/measurement_config.py
55
+ scpi_control/models.py
56
+ scpi_control/oscilloscope.py
57
+ scpi_control/power_supply.py
58
+ scpi_control/power_supply_output.py
59
+ scpi_control/protocol_decode.py
60
+ scpi_control/psu_data_logger.py
61
+ scpi_control/psu_models.py
62
+ scpi_control/psu_scpi_commands.py
63
+ scpi_control/py.typed
64
+ scpi_control/reference_waveform.py
65
+ scpi_control/scpi_commands.py
66
+ scpi_control/screen_capture.py
67
+ scpi_control/trigger.py
68
+ scpi_control/vector_graphics.py
69
+ scpi_control/waveform.py
70
+ scpi_control/connection/__init__.py
71
+ scpi_control/connection/base.py
72
+ scpi_control/connection/mock.py
73
+ scpi_control/connection/socket.py
74
+ scpi_control/connection/visa_connection.py
75
+ scpi_control/gui/__init__.py
76
+ scpi_control/gui/app.py
77
+ scpi_control/gui/connection_manager.py
78
+ scpi_control/gui/live_view_worker.py
79
+ scpi_control/gui/main_window.py
80
+ scpi_control/gui/vnc_window.py
81
+ scpi_control/gui/waveform_capture_worker.py
82
+ scpi_control/gui/utils/validators.py
83
+ scpi_control/gui/widgets/__init__.py
84
+ scpi_control/gui/widgets/channel_control.py
85
+ scpi_control/gui/widgets/cursor_panel.py
86
+ scpi_control/gui/widgets/error_dialog.py
87
+ scpi_control/gui/widgets/fft_display.py
88
+ scpi_control/gui/widgets/math_panel.py
89
+ scpi_control/gui/widgets/measurement_marker.py
90
+ scpi_control/gui/widgets/measurement_panel.py
91
+ scpi_control/gui/widgets/protocol_decode_panel.py
92
+ scpi_control/gui/widgets/psu_control.py
93
+ scpi_control/gui/widgets/reference_panel.py
94
+ scpi_control/gui/widgets/scope_web_view.py
95
+ scpi_control/gui/widgets/terminal_widget.py
96
+ scpi_control/gui/widgets/timebase_control.py
97
+ scpi_control/gui/widgets/trigger_control.py
98
+ scpi_control/gui/widgets/vector_graphics_panel.py
99
+ scpi_control/gui/widgets/visual_measurement_panel.py
100
+ scpi_control/gui/widgets/waveform_display.py
101
+ scpi_control/gui/widgets/waveform_display_pg.py
102
+ scpi_control/gui/widgets/measurement_markers/__init__.py
103
+ scpi_control/gui/widgets/measurement_markers/frequency_marker.py
104
+ scpi_control/gui/widgets/measurement_markers/timing_marker.py
105
+ scpi_control/gui/widgets/measurement_markers/voltage_marker.py
106
+ scpi_control/protocol_decoders/__init__.py
107
+ scpi_control/protocol_decoders/i2c_decoder.py
108
+ scpi_control/protocol_decoders/spi_decoder.py
109
+ scpi_control/protocol_decoders/uart_decoder.py
110
+ scpi_control/report_generator/__init__.py
111
+ scpi_control/report_generator/app.py
112
+ scpi_control/report_generator/main_window.py
113
+ scpi_control/report_generator/generators/__init__.py
114
+ scpi_control/report_generator/generators/base.py
115
+ scpi_control/report_generator/generators/markdown_generator.py
116
+ scpi_control/report_generator/generators/pdf_generator.py
117
+ scpi_control/report_generator/llm/__init__.py
118
+ scpi_control/report_generator/llm/analyzer.py
119
+ scpi_control/report_generator/llm/client.py
120
+ scpi_control/report_generator/llm/context_builder.py
121
+ scpi_control/report_generator/llm/prompts.py
122
+ scpi_control/report_generator/models/__init__.py
123
+ scpi_control/report_generator/models/app_settings.py
124
+ scpi_control/report_generator/models/criteria.py
125
+ scpi_control/report_generator/models/plot_style.py
126
+ scpi_control/report_generator/models/report_data.py
127
+ scpi_control/report_generator/models/report_options.py
128
+ scpi_control/report_generator/models/template.py
129
+ scpi_control/report_generator/models/test_types.py
130
+ scpi_control/report_generator/utils/__init__.py
131
+ scpi_control/report_generator/utils/image_handler.py
132
+ scpi_control/report_generator/utils/waveform_analyzer.py
133
+ scpi_control/report_generator/utils/waveform_loader.py
134
+ scpi_control/report_generator/widgets/__init__.py
135
+ scpi_control/report_generator/widgets/ai_analysis_panel.py
136
+ scpi_control/report_generator/widgets/chat_sidebar.py
137
+ scpi_control/report_generator/widgets/llm_settings_dialog.py
138
+ scpi_control/report_generator/widgets/metadata_panel.py
139
+ scpi_control/report_generator/widgets/pdf_preview_dialog.py
140
+ scpi_control/report_generator/widgets/report_options_dialog.py
141
+ scpi_control/report_generator/widgets/template_manager_dialog.py
142
+ siglent/__init__.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ siglent-gui = scpi_control.gui.app:main
3
+ siglent-report-generator = scpi_control.report_generator.app:main
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "SCPI-Instrument-Control"
7
- version = "1.0.0"
7
+ version = "1.0.1"
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"
@@ -122,18 +122,19 @@ all = [
122
122
  ]
123
123
 
124
124
  [project.scripts]
125
- siglent-gui = "siglent.gui.app:main"
126
- siglent-report-generator = "siglent.report_generator.app:main"
125
+ siglent-gui = "scpi_control.gui.app:main"
126
+ siglent-report-generator = "scpi_control.report_generator.app:main"
127
127
 
128
128
  [tool.setuptools]
129
129
  include-package-data = true
130
130
 
131
131
  [tool.setuptools.package-data]
132
132
  siglent = ["py.typed"]
133
+ scpi_control = ["py.typed"]
133
134
 
134
135
  [tool.setuptools.packages.find]
135
136
  where = ["."]
136
- include = ["siglent*"]
137
+ include = ["siglent*", "scpi_control*"]
137
138
 
138
139
  [tool.black]
139
140
  line-length = 100
@@ -158,7 +159,7 @@ filterwarnings = [
158
159
  ]
159
160
 
160
161
  [tool.coverage.run]
161
- source = ["siglent"]
162
+ source = ["siglent", "scpi_control"]
162
163
  omit = [
163
164
  "*/tests/*",
164
165
  "*/test_*.py",
@@ -0,0 +1,49 @@
1
+ """SCPI Instrument Control Package
2
+
3
+ This package provides programmatic control for SCPI-compatible test equipment
4
+ via Ethernet/LAN connection.
5
+
6
+ Supported equipment:
7
+ - Oscilloscopes (Siglent SDS series and SCPI-compatible models)
8
+ - Function Generators / AWGs (Siglent SDG series and SCPI-compatible models)
9
+ - Power Supplies (Siglent SPD series and SCPI-compatible models)
10
+
11
+ For high-level automation and data collection, see the automation module:
12
+ from scpi_control.automation import DataCollector, TriggerWaitCollector
13
+
14
+ For power supply control:
15
+ from scpi_control import PowerSupply
16
+
17
+ For function generator control:
18
+ from scpi_control import FunctionGenerator
19
+
20
+ For automated test report generation:
21
+ from scpi_control.report_generator import ReportGenerator
22
+ """
23
+
24
+ __version__ = "1.0.1"
25
+
26
+ from scpi_control.exceptions import CommandError, SiglentConnectionError, SiglentError, SiglentTimeoutError
27
+ from scpi_control.oscilloscope import Oscilloscope
28
+
29
+ # Power supply support (stable as of v0.5.0)
30
+ from scpi_control.power_supply import PowerSupply
31
+ from scpi_control.psu_data_logger import PSUDataLogger, TimedPSULogger
32
+
33
+ # Function generator support (new in v0.6.0, stable in v1.0.0)
34
+ from scpi_control.function_generator import FunctionGenerator
35
+
36
+ __all__ = [
37
+ # Core features
38
+ "Oscilloscope",
39
+ "SiglentError",
40
+ "SiglentConnectionError",
41
+ "SiglentTimeoutError",
42
+ "CommandError",
43
+ # Power supply features
44
+ "PowerSupply",
45
+ "PSUDataLogger",
46
+ "TimedPSULogger",
47
+ # Function generator features
48
+ "FunctionGenerator",
49
+ ]
@@ -0,0 +1,401 @@
1
+ """Advanced analysis tools for waveform data."""
2
+
3
+ import logging
4
+ from dataclasses import dataclass
5
+ from typing import Optional, Tuple
6
+
7
+ import numpy as np
8
+ from scipy import signal
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class FFTResult:
15
+ """Result of FFT analysis.
16
+
17
+ Attributes:
18
+ frequency: Frequency array (Hz)
19
+ magnitude: Magnitude array (linear or dB)
20
+ phase: Phase array (radians)
21
+ window: Window function used
22
+ sample_rate: Sample rate (Hz)
23
+ magnitude_db: Whether magnitude is in dB
24
+ """
25
+
26
+ frequency: np.ndarray
27
+ magnitude: np.ndarray
28
+ phase: np.ndarray
29
+ window: str
30
+ sample_rate: float
31
+ magnitude_db: bool = True
32
+
33
+ def get_peak_frequency(self, num_peaks: int = 1) -> list:
34
+ """Get the peak frequencies.
35
+
36
+ Args:
37
+ num_peaks: Number of peaks to find
38
+
39
+ Returns:
40
+ List of (frequency, magnitude) tuples
41
+ """
42
+ # Find peaks
43
+ peaks, _ = signal.find_peaks(self.magnitude, height=0)
44
+
45
+ # Sort by magnitude (descending)
46
+ if len(peaks) > 0:
47
+ peak_magnitudes = self.magnitude[peaks]
48
+ sorted_indices = np.argsort(peak_magnitudes)[::-1]
49
+ top_peaks = peaks[sorted_indices[:num_peaks]]
50
+
51
+ results = []
52
+ for peak_idx in top_peaks:
53
+ freq = self.frequency[peak_idx]
54
+ mag = self.magnitude[peak_idx]
55
+ results.append((freq, mag))
56
+
57
+ return results
58
+ else:
59
+ return []
60
+
61
+
62
+ class FFTAnalyzer:
63
+ """FFT analyzer for frequency domain analysis of waveforms."""
64
+
65
+ WINDOW_FUNCTIONS = {
66
+ "rectangular": np.ones,
67
+ "hanning": np.hanning,
68
+ "hamming": np.hamming,
69
+ "blackman": np.blackman,
70
+ "bartlett": np.bartlett,
71
+ "flattop": signal.windows.flattop,
72
+ }
73
+
74
+ def __init__(self):
75
+ """Initialize FFT analyzer."""
76
+ logger.info("FFT analyzer initialized")
77
+
78
+ def compute_fft(self, waveform, window: str = "hanning", output_db: bool = True, detrend: bool = True) -> Optional[FFTResult]:
79
+ """Compute FFT of a waveform.
80
+
81
+ Args:
82
+ waveform: Input waveform
83
+ window: Window function name ('rectangular', 'hanning', 'hamming', 'blackman', 'bartlett', 'flattop')
84
+ output_db: If True, output magnitude in dB; otherwise linear
85
+ detrend: If True, remove DC component before FFT
86
+
87
+ Returns:
88
+ FFTResult or None if error
89
+ """
90
+ if waveform is None:
91
+ logger.warning("Cannot compute FFT on None waveform")
92
+ return None
93
+
94
+ try:
95
+ # Get voltage data
96
+ voltage = waveform.voltage
97
+ time = waveform.time
98
+
99
+ if len(voltage) < 2:
100
+ logger.warning("Waveform too short for FFT")
101
+ return None
102
+
103
+ # Calculate sample rate
104
+ dt = np.mean(np.diff(time))
105
+ sample_rate = 1.0 / dt
106
+
107
+ # Detrend if requested (remove DC component)
108
+ if detrend:
109
+ voltage = voltage - np.mean(voltage)
110
+
111
+ # Apply window function
112
+ if window.lower() in self.WINDOW_FUNCTIONS:
113
+ window_func = self.WINDOW_FUNCTIONS[window.lower()]
114
+ windowed_voltage = voltage * window_func(len(voltage))
115
+ else:
116
+ logger.warning(f"Unknown window function '{window}', using rectangular")
117
+ windowed_voltage = voltage
118
+
119
+ # Compute FFT
120
+ fft_result = np.fft.rfft(windowed_voltage)
121
+ frequencies = np.fft.rfftfreq(len(voltage), dt)
122
+
123
+ # Calculate magnitude and phase
124
+ magnitude_linear = np.abs(fft_result)
125
+ phase = np.angle(fft_result)
126
+
127
+ # Convert to dB if requested
128
+ if output_db:
129
+ # Avoid log(0) by using a small epsilon
130
+ epsilon = 1e-12
131
+ magnitude = 20 * np.log10(magnitude_linear + epsilon)
132
+ else:
133
+ magnitude = magnitude_linear
134
+
135
+ result = FFTResult(
136
+ frequency=frequencies,
137
+ magnitude=magnitude,
138
+ phase=phase,
139
+ window=window,
140
+ sample_rate=sample_rate,
141
+ magnitude_db=output_db,
142
+ )
143
+
144
+ logger.info(f"FFT computed: {len(frequencies)} frequency bins, " f"sample rate {sample_rate/1e6:.3f} MHz, window={window}")
145
+
146
+ return result
147
+
148
+ except Exception as e:
149
+ logger.error(f"FFT computation error: {e}")
150
+ return None
151
+
152
+ def compute_power_spectrum(self, waveform, window: str = "hanning", nperseg: Optional[int] = None) -> Optional[Tuple[np.ndarray, np.ndarray]]:
153
+ """Compute power spectral density using Welch's method.
154
+
155
+ Args:
156
+ waveform: Input waveform
157
+ window: Window function name
158
+ nperseg: Length of each segment (default: 256)
159
+
160
+ Returns:
161
+ Tuple of (frequencies, power_spectrum) or None if error
162
+ """
163
+ if waveform is None:
164
+ return None
165
+
166
+ try:
167
+ voltage = waveform.voltage
168
+ time = waveform.time
169
+ dt = np.mean(np.diff(time))
170
+ sample_rate = 1.0 / dt
171
+
172
+ if nperseg is None:
173
+ nperseg = min(256, len(voltage) // 4)
174
+
175
+ # Compute power spectral density using Welch's method
176
+ frequencies, psd = signal.welch(voltage, fs=sample_rate, window=window, nperseg=nperseg, scaling="density")
177
+
178
+ logger.info(f"Power spectrum computed using Welch's method")
179
+
180
+ return frequencies, psd
181
+
182
+ except Exception as e:
183
+ logger.error(f"Power spectrum computation error: {e}")
184
+ return None
185
+
186
+ def compute_spectrogram(self, waveform, window: str = "hanning", nperseg: Optional[int] = None) -> Optional[Tuple[np.ndarray, np.ndarray, np.ndarray]]:
187
+ """Compute spectrogram (time-frequency representation).
188
+
189
+ Args:
190
+ waveform: Input waveform
191
+ window: Window function name
192
+ nperseg: Length of each segment (default: 256)
193
+
194
+ Returns:
195
+ Tuple of (frequencies, times, spectrogram) or None if error
196
+ """
197
+ if waveform is None:
198
+ return None
199
+
200
+ try:
201
+ voltage = waveform.voltage
202
+ time = waveform.time
203
+ dt = np.mean(np.diff(time))
204
+ sample_rate = 1.0 / dt
205
+
206
+ if nperseg is None:
207
+ nperseg = min(256, len(voltage) // 4)
208
+
209
+ # Compute spectrogram
210
+ frequencies, times, Sxx = signal.spectrogram(voltage, fs=sample_rate, window=window, nperseg=nperseg)
211
+
212
+ logger.info(f"Spectrogram computed: {len(frequencies)}x{len(times)} matrix")
213
+
214
+ return frequencies, times, Sxx
215
+
216
+ except Exception as e:
217
+ logger.error(f"Spectrogram computation error: {e}")
218
+ return None
219
+
220
+ def apply_bandpass_filter(self, waveform, lowcut: float, highcut: float, order: int = 5):
221
+ """Apply bandpass filter to waveform.
222
+
223
+ Args:
224
+ waveform: Input waveform
225
+ lowcut: Low frequency cutoff (Hz)
226
+ highcut: High frequency cutoff (Hz)
227
+ order: Filter order
228
+
229
+ Returns:
230
+ Filtered waveform or None if error
231
+ """
232
+ if waveform is None:
233
+ return None
234
+
235
+ try:
236
+ voltage = waveform.voltage
237
+ time = waveform.time
238
+ dt = np.mean(np.diff(time))
239
+ sample_rate = 1.0 / dt
240
+
241
+ # Design Butterworth bandpass filter
242
+ nyquist = sample_rate / 2.0
243
+ low = lowcut / nyquist
244
+ high = highcut / nyquist
245
+
246
+ if low <= 0 or high >= 1:
247
+ logger.error("Filter frequencies out of valid range")
248
+ return None
249
+
250
+ sos = signal.butter(order, [low, high], btype="band", output="sos")
251
+
252
+ # Apply filter
253
+ filtered_voltage = signal.sosfilt(sos, voltage)
254
+
255
+ # Create filtered waveform
256
+ result = type(waveform)(time=time, voltage=filtered_voltage, channel=f"{waveform.channel}_FILTERED")
257
+
258
+ logger.info(f"Bandpass filter applied: {lowcut:.2f}-{highcut:.2f} Hz, order={order}")
259
+
260
+ return result
261
+
262
+ except Exception as e:
263
+ logger.error(f"Bandpass filter error: {e}")
264
+ return None
265
+
266
+ def apply_lowpass_filter(self, waveform, cutoff: float, order: int = 5):
267
+ """Apply lowpass filter to waveform.
268
+
269
+ Args:
270
+ waveform: Input waveform
271
+ cutoff: Cutoff frequency (Hz)
272
+ order: Filter order
273
+
274
+ Returns:
275
+ Filtered waveform or None if error
276
+ """
277
+ if waveform is None:
278
+ return None
279
+
280
+ try:
281
+ voltage = waveform.voltage
282
+ time = waveform.time
283
+ dt = np.mean(np.diff(time))
284
+ sample_rate = 1.0 / dt
285
+
286
+ # Design Butterworth lowpass filter
287
+ nyquist = sample_rate / 2.0
288
+ normal_cutoff = cutoff / nyquist
289
+
290
+ if normal_cutoff <= 0 or normal_cutoff >= 1:
291
+ logger.error("Filter cutoff out of valid range")
292
+ return None
293
+
294
+ sos = signal.butter(order, normal_cutoff, btype="low", output="sos")
295
+
296
+ # Apply filter
297
+ filtered_voltage = signal.sosfilt(sos, voltage)
298
+
299
+ # Create filtered waveform
300
+ result = type(waveform)(time=time, voltage=filtered_voltage, channel=f"{waveform.channel}_FILTERED")
301
+
302
+ logger.info(f"Lowpass filter applied: {cutoff:.2f} Hz, order={order}")
303
+
304
+ return result
305
+
306
+ except Exception as e:
307
+ logger.error(f"Lowpass filter error: {e}")
308
+ return None
309
+
310
+ def apply_highpass_filter(self, waveform, cutoff: float, order: int = 5):
311
+ """Apply highpass filter to waveform.
312
+
313
+ Args:
314
+ waveform: Input waveform
315
+ cutoff: Cutoff frequency (Hz)
316
+ order: Filter order
317
+
318
+ Returns:
319
+ Filtered waveform or None if error
320
+ """
321
+ if waveform is None:
322
+ return None
323
+
324
+ try:
325
+ voltage = waveform.voltage
326
+ time = waveform.time
327
+ dt = np.mean(np.diff(time))
328
+ sample_rate = 1.0 / dt
329
+
330
+ # Design Butterworth highpass filter
331
+ nyquist = sample_rate / 2.0
332
+ normal_cutoff = cutoff / nyquist
333
+
334
+ if normal_cutoff <= 0 or normal_cutoff >= 1:
335
+ logger.error("Filter cutoff out of valid range")
336
+ return None
337
+
338
+ sos = signal.butter(order, normal_cutoff, btype="high", output="sos")
339
+
340
+ # Apply filter
341
+ filtered_voltage = signal.sosfilt(sos, voltage)
342
+
343
+ # Create filtered waveform
344
+ result = type(waveform)(time=time, voltage=filtered_voltage, channel=f"{waveform.channel}_FILTERED")
345
+
346
+ logger.info(f"Highpass filter applied: {cutoff:.2f} Hz, order={order}")
347
+
348
+ return result
349
+
350
+ except Exception as e:
351
+ logger.error(f"Highpass filter error: {e}")
352
+ return None
353
+
354
+ @staticmethod
355
+ def calculate_thd(fft_result: FFTResult, fundamental_freq: float, num_harmonics: int = 5) -> Optional[float]:
356
+ """Calculate Total Harmonic Distortion (THD).
357
+
358
+ Args:
359
+ fft_result: FFT result
360
+ fundamental_freq: Fundamental frequency (Hz)
361
+ num_harmonics: Number of harmonics to include
362
+
363
+ Returns:
364
+ THD in percent or None if error
365
+ """
366
+ try:
367
+ # Find fundamental frequency bin
368
+ freq_resolution = fft_result.frequency[1] - fft_result.frequency[0]
369
+ fund_idx = int(round(fundamental_freq / freq_resolution))
370
+
371
+ if fund_idx >= len(fft_result.magnitude):
372
+ logger.error("Fundamental frequency out of range")
373
+ return None
374
+
375
+ # Get fundamental magnitude (linear)
376
+ if fft_result.magnitude_db:
377
+ fund_mag = 10 ** (fft_result.magnitude[fund_idx] / 20.0)
378
+ else:
379
+ fund_mag = fft_result.magnitude[fund_idx]
380
+
381
+ # Calculate harmonic magnitudes
382
+ harmonic_power = 0.0
383
+ for n in range(2, num_harmonics + 2):
384
+ harmonic_idx = n * fund_idx
385
+ if harmonic_idx < len(fft_result.magnitude):
386
+ if fft_result.magnitude_db:
387
+ harm_mag = 10 ** (fft_result.magnitude[harmonic_idx] / 20.0)
388
+ else:
389
+ harm_mag = fft_result.magnitude[harmonic_idx]
390
+ harmonic_power += harm_mag**2
391
+
392
+ # Calculate THD
393
+ thd = 100.0 * np.sqrt(harmonic_power) / fund_mag
394
+
395
+ logger.info(f"THD calculated: {thd:.2f}% (fundamental={fundamental_freq:.2f} Hz)")
396
+
397
+ return thd
398
+
399
+ except Exception as e:
400
+ logger.error(f"THD calculation error: {e}")
401
+ return None