mso4000 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: mso4000
3
+ Version: 0.1.0
4
+ Summary: Simple library for Tektronix MSO4000 series oscilloscopes
5
+ Author-email: Asaf Ayalon <ayalon.asaf.c0@s.mail.nagoya-u.ac.jp>
6
+ License-Expression: MIT
7
+ Keywords: oscilloscope,tektronix,mso4000,visa,measurement
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.6
12
+ Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Classifier: Topic :: Scientific/Engineering :: Physics
19
+ Requires-Python: >=3.6
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pyvisa>=1.11.0
23
+ Requires-Dist: numpy>=1.16.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: setuptools; extra == "dev"
26
+ Requires-Dist: wheel; extra == "dev"
27
+ Requires-Dist: twine; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # MSO4000 Library
31
+
32
+ Simple, clean library for Tektronix MSO4000 series oscilloscopes.
33
+
34
+ ## Features
35
+
36
+ - āœ… Connection checking
37
+ - āœ… Single waveform capture
38
+ - āœ… Multiple waveform recording to CSV
39
+ - āœ… Simple, clean API
40
+
41
+ ## Installation
42
+
43
+ Install from PyPI:
44
+
45
+ ```bash
46
+ pip install mso4000
47
+ ```
48
+
49
+ ## Quick Start
50
+
51
+ ### 1. Check Connection
52
+
53
+ ```python
54
+ from mso4000 import check_connection
55
+
56
+ success, info = check_connection()
57
+ if success:
58
+ print(f"Connected: {info}")
59
+ else:
60
+ print(f"Error: {info}")
61
+ ```
62
+
63
+ ### 2. Record Waveforms to CSV (One-liner)
64
+
65
+ ```python
66
+ from mso4000 import quick_record
67
+
68
+ # Record 10 waveforms and save to CSV
69
+ filename = quick_record(channel=1, max_records=10)
70
+ print(f"Saved to: {filename}")
71
+ ```
72
+
73
+ ### 3. Full Control
74
+
75
+ ```python
76
+ from mso4000 import MSO4000
77
+
78
+ # Create scope interface
79
+ scope = MSO4000(channel=1)
80
+
81
+ # Connect
82
+ if scope.connect():
83
+ # Capture single waveform
84
+ waveform = scope.capture_waveform()
85
+ print(f"Captured {len(waveform)} samples")
86
+
87
+ # Or record multiple to CSV
88
+ filename = scope.record_to_csv(max_records=20)
89
+ print(f"Saved to: {filename}")
90
+
91
+ # Close connection
92
+ scope.close()
93
+ ```
94
+
95
+ ## API Reference
96
+
97
+ ### `MSO4000` Class
98
+
99
+ #### `__init__(channel=1)`
100
+ Initialize the oscilloscope interface.
101
+
102
+ **Parameters:**
103
+ - `channel` (int): Channel number (1-4), default 1
104
+
105
+ #### `check_connection() -> Tuple[bool, Optional[str]]`
106
+ Check if oscilloscope is connected.
107
+
108
+ **Returns:**
109
+ - `(True, device_info)` if connected
110
+ - `(False, error_message)` if not connected
111
+
112
+ #### `connect() -> bool`
113
+ Connect to oscilloscope and configure for waveform transfer.
114
+
115
+ **Returns:**
116
+ - `True` if successful
117
+ - `False` if failed
118
+
119
+ #### `capture_waveform() -> Optional[np.ndarray]`
120
+ Capture a single waveform.
121
+
122
+ **Returns:**
123
+ - `numpy.ndarray` of voltage values, or `None` if failed
124
+
125
+ #### `record_to_csv(filename=None, max_records=None, delay=0.5) -> Optional[str]`
126
+ Record multiple waveforms and save to CSV.
127
+
128
+ **Parameters:**
129
+ - `filename` (str, optional): Output filename (auto-generated if None)
130
+ - `max_records` (int, optional): Maximum waveforms to record (None = until Ctrl+C)
131
+ - `delay` (float): Delay between captures in seconds (default 0.5)
132
+
133
+ **Returns:**
134
+ - Filename of saved CSV, or `None` if failed
135
+
136
+ #### `close()`
137
+ Close connection to oscilloscope.
138
+
139
+ ### Convenience Functions
140
+
141
+ #### `check_connection() -> Tuple[bool, Optional[str]]`
142
+ Quick function to check connection.
143
+
144
+ #### `quick_record(channel=1, max_records=None, filename=None) -> Optional[str]`
145
+ Quick function to connect and record waveforms.
146
+
147
+ **Parameters:**
148
+ - `channel` (int): Channel number (1-4)
149
+ - `max_records` (int, optional): Maximum waveforms (None = until Ctrl+C)
150
+ - `filename` (str, optional): Output filename (auto-generated if None)
151
+
152
+ **Returns:**
153
+ - Filename of saved CSV, or `None` if failed
154
+
155
+ ## CSV Output Format
156
+
157
+ The CSV file contains:
158
+ - **Column 1:** Sample index (0 to N-1)
159
+ - **Column 2:** Time in seconds (relative to waveform start)
160
+ - **Columns 3+:** Voltage values for each recorded waveform
161
+
162
+ Example:
163
+ ```csv
164
+ Sample_Index,Time_sec,Waveform_1_V,Waveform_2_V,Waveform_3_V
165
+ 0,0.000000,-0.123456,0.234567,-0.345678
166
+ 1,0.000010,-0.125432,0.236789,-0.347890
167
+ ...
168
+ ```
169
+
170
+ ## Examples
171
+
172
+ See `example_mso4000.py` for complete examples.
173
+
174
+ ## Requirements
175
+
176
+ - Python 3.6+
177
+ - pyvisa
178
+ - numpy
179
+
180
+ ## Notes
181
+
182
+ - The library automatically selects USB resources (skips serial ports)
183
+ - Uses binary encoding (RIBINARY) for efficient data transfer
184
+ - Default timeout is 30 seconds for waveform transfers
185
+ - Press Ctrl+C to stop recording
186
+
187
+ ## License
188
+
189
+ MIT License - See LICENSE file for details
190
+
@@ -0,0 +1,6 @@
1
+ mso4000.py,sha256=z6MNxykotPq9fFsd681l3uCDudVYjOqSyM_uB33YwJI,10448
2
+ mso4000-0.1.0.dist-info/licenses/LICENSE,sha256=8_up1FX6vk2DRcusQEZ4pWJGkgkjvEkD14xB1hdLe3c,1067
3
+ mso4000-0.1.0.dist-info/METADATA,sha256=w3CXDeUypQwCPFNejVe6qqzICsumThfDQmxcROcNcDU,4666
4
+ mso4000-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
+ mso4000-0.1.0.dist-info/top_level.txt,sha256=fl0KrUGzNDpxXFv9VzGSrI5WswtF3DqQeLMdHQ3slN0,8
6
+ mso4000-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1 @@
1
+ mso4000
mso4000.py ADDED
@@ -0,0 +1,313 @@
1
+ """
2
+ Simple library for Tektronix MSO4000 series oscilloscopes
3
+ Provides connection checking and waveform recording to CSV
4
+ """
5
+ import pyvisa
6
+ import numpy as np
7
+ import time
8
+ import csv
9
+ from datetime import datetime
10
+ from typing import Optional, Tuple, List
11
+
12
+
13
+ class MSO4000:
14
+ """Simple interface for Tektronix MSO4000 series oscilloscopes"""
15
+
16
+ def __init__(self, channel: int = 1):
17
+ """
18
+ Initialize MSO4000 interface
19
+
20
+ Args:
21
+ channel: Channel number (1-4)
22
+ """
23
+ self.channel = channel
24
+ self.scope = None
25
+ self.rm = None
26
+ self.timebase = None
27
+ self.ch_scale = None
28
+
29
+ def check_connection(self) -> Tuple[bool, Optional[str]]:
30
+ """
31
+ Check if oscilloscope is connected and accessible
32
+
33
+ Returns:
34
+ Tuple of (success: bool, device_info: str or None)
35
+ """
36
+ try:
37
+ self.rm = pyvisa.ResourceManager()
38
+ resources = self.rm.list_resources()
39
+
40
+ # Prefer USB resources
41
+ usb_resources = [r for r in resources if r.startswith('USB')]
42
+ if not usb_resources:
43
+ return False, "No USB oscilloscope found"
44
+
45
+ resource_name = usb_resources[0]
46
+ self.scope = self.rm.open_resource(resource_name)
47
+ self.scope.timeout = 5000
48
+
49
+ # Query identification
50
+ idn = self.scope.query("*IDN?")
51
+ device_info = idn.strip()
52
+
53
+ return True, device_info
54
+
55
+ except Exception as e:
56
+ return False, f"Connection error: {e}"
57
+
58
+ def connect(self) -> bool:
59
+ """
60
+ Connect to oscilloscope and configure for waveform transfer
61
+
62
+ Returns:
63
+ True if successful, False otherwise
64
+ """
65
+ success, info = self.check_connection()
66
+ if not success:
67
+ print(f"āŒ {info}")
68
+ return False
69
+
70
+ try:
71
+ print(f"āœ“ Connected to: {info}")
72
+
73
+ # Configure for waveform transfer
74
+ self.scope.write("*CLS")
75
+ self.scope.write(f":DATA:SOURCE CH{self.channel}")
76
+ self.scope.write(":DATA:ENCDG RIBINARY")
77
+ self.scope.write(":WFMOUTPRE:BYT_NR 2")
78
+ self.scope.write(":DATA INIT")
79
+
80
+ # Get scale info
81
+ self.timebase = float(self.scope.query(":HORIZONTAL:SCALE?"))
82
+ self.ch_scale = float(self.scope.query(f":CH{self.channel}:SCALE?"))
83
+
84
+ print(f"āœ“ Configured: Timebase={self.timebase} s/div, CH{self.channel}={self.ch_scale} V/div")
85
+ return True
86
+
87
+ except Exception as e:
88
+ print(f"āŒ Configuration error: {e}")
89
+ return False
90
+
91
+ def capture_waveform(self) -> Optional[np.ndarray]:
92
+ """
93
+ Capture a single waveform from the oscilloscope
94
+
95
+ Returns:
96
+ Voltage array (numpy array) or None if failed
97
+ """
98
+ if not self.scope:
99
+ print("āŒ Not connected. Call connect() first.")
100
+ return None
101
+
102
+ try:
103
+ self.scope.timeout = 30000 # 30 seconds for data transfer
104
+
105
+ # Trigger and get waveform
106
+ self.scope.write(":TRIGGER:FORCE")
107
+ time.sleep(0.2)
108
+
109
+ # Get data using write + read_raw for binary data
110
+ self.scope.write("CURVE?")
111
+ curve_data_bytes = self.scope.read_raw()
112
+
113
+ # Parse binary data
114
+ curve_data_str = curve_data_bytes.decode('latin1', errors='ignore')
115
+
116
+ if curve_data_str.startswith('#'):
117
+ digit_count = int(curve_data_str[1])
118
+ byte_count = int(curve_data_str[2:2+digit_count])
119
+ header_len = 2 + digit_count
120
+
121
+ # Extract binary data
122
+ binary_data = curve_data_bytes[header_len:header_len+byte_count]
123
+
124
+ # Convert to array (16-bit signed integers, big-endian)
125
+ voltage_array = np.frombuffer(binary_data, dtype='>i2')
126
+
127
+ # Scale to voltage
128
+ voltage_scaled = (voltage_array / 32768.0) * self.ch_scale * 5.0
129
+ else:
130
+ # ASCII fallback
131
+ values = [float(x) for x in curve_data_str.split(',')]
132
+ voltage_scaled = np.array(values)
133
+
134
+ return voltage_scaled
135
+
136
+ except Exception as e:
137
+ print(f"āŒ Error capturing waveform: {e}")
138
+ return None
139
+
140
+ def record_to_csv(self, filename: Optional[str] = None,
141
+ max_records: Optional[int] = None,
142
+ delay: float = 0.5) -> Optional[str]:
143
+ """
144
+ Record multiple waveforms and save to CSV
145
+
146
+ Args:
147
+ filename: Output CSV filename (auto-generated if None)
148
+ max_records: Maximum number of waveforms to record (None = until interrupted)
149
+ delay: Delay between captures in seconds
150
+
151
+ Returns:
152
+ Filename of saved CSV or None if failed
153
+ """
154
+ if not self.scope:
155
+ print("āŒ Not connected. Call connect() first.")
156
+ return None
157
+
158
+ if filename is None:
159
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
160
+ filename = f"waveform_data_{timestamp}.csv"
161
+
162
+ all_data = []
163
+ timestamps = []
164
+ record_count = 0
165
+
166
+ print(f"\nšŸ“ Recording waveforms... (Press Ctrl+C to stop)")
167
+ print(f" Output: {filename}\n")
168
+
169
+ try:
170
+ while True:
171
+ if max_records and record_count >= max_records:
172
+ print(f"\nāœ“ Record limit reached ({max_records} records)")
173
+ break
174
+
175
+ print(f"[{record_count+1}] Capturing... ", end="", flush=True)
176
+
177
+ voltage_data = self.capture_waveform()
178
+
179
+ if voltage_data is not None:
180
+ all_data.append(voltage_data)
181
+ timestamps.append(time.time())
182
+ record_count += 1
183
+
184
+ min_v = voltage_data.min()
185
+ max_v = voltage_data.max()
186
+ mean_v = voltage_data.mean()
187
+ print(f"āœ“ {len(voltage_data):6d} samples | "
188
+ f"Min: {min_v:8.4f}V | Max: {max_v:8.4f}V | Mean: {mean_v:8.4f}V")
189
+
190
+ time.sleep(delay)
191
+ else:
192
+ print("āœ— FAILED")
193
+ time.sleep(1)
194
+
195
+ except KeyboardInterrupt:
196
+ print("\nā¹ Recording interrupted by user")
197
+
198
+ if not all_data:
199
+ print("āŒ No data to export!")
200
+ return None
201
+
202
+ # Export to CSV
203
+ print(f"\nšŸ’¾ Exporting to {filename}...")
204
+
205
+ try:
206
+ with open(filename, 'w', newline='') as csvfile:
207
+ max_samples = max(len(wf) for wf in all_data)
208
+
209
+ # Create header
210
+ headers = ['Sample_Index', 'Time_sec'] + \
211
+ [f'Waveform_{i+1}_V' for i in range(record_count)]
212
+
213
+ writer = csv.writer(csvfile)
214
+ writer.writerow(headers)
215
+
216
+ # Calculate time per point
217
+ num_points = len(all_data[0]) if all_data else 1
218
+ time_per_point = (self.timebase * 10.0) / num_points
219
+
220
+ # Write data
221
+ for sample_idx in range(max_samples):
222
+ row = [sample_idx, sample_idx * time_per_point]
223
+
224
+ for waveform in all_data:
225
+ if sample_idx < len(waveform):
226
+ row.append(waveform[sample_idx])
227
+ else:
228
+ row.append('')
229
+
230
+ writer.writerow(row)
231
+
232
+ print(f"āœ“ Exported {record_count} waveforms, {max_samples} samples each")
233
+
234
+ import os
235
+ file_size = os.path.getsize(filename)
236
+ print(f"āœ“ File size: {file_size / 1024 / 1024:.2f} MB")
237
+
238
+ return filename
239
+
240
+ except Exception as e:
241
+ print(f"āŒ Error exporting: {e}")
242
+ return None
243
+
244
+ def close(self):
245
+ """Close connection to oscilloscope"""
246
+ if self.scope:
247
+ self.scope.close()
248
+ self.scope = None
249
+ print("āœ“ Connection closed")
250
+
251
+
252
+ # Convenience functions
253
+ def check_connection() -> Tuple[bool, Optional[str]]:
254
+ """
255
+ Quick function to check if oscilloscope is connected
256
+
257
+ Returns:
258
+ Tuple of (success: bool, device_info: str or None)
259
+ """
260
+ scope = MSO4000()
261
+ return scope.check_connection()
262
+
263
+
264
+ def quick_record(channel: int = 1, max_records: Optional[int] = None,
265
+ filename: Optional[str] = None) -> Optional[str]:
266
+ """
267
+ Quick function to connect and record waveforms
268
+
269
+ Args:
270
+ channel: Channel number (1-4)
271
+ max_records: Maximum number of waveforms (None = until interrupted)
272
+ filename: Output CSV filename (auto-generated if None)
273
+
274
+ Returns:
275
+ Filename of saved CSV or None if failed
276
+ """
277
+ scope = MSO4000(channel=channel)
278
+
279
+ if not scope.connect():
280
+ return None
281
+
282
+ try:
283
+ filename = scope.record_to_csv(filename=filename, max_records=max_records)
284
+ return filename
285
+ finally:
286
+ scope.close()
287
+
288
+
289
+ if __name__ == "__main__":
290
+ # Example usage
291
+ print("=" * 70)
292
+ print("MSO4000 Library - Example Usage")
293
+ print("=" * 70 + "\n")
294
+
295
+ # Check connection
296
+ print("[1] Checking connection...")
297
+ success, info = check_connection()
298
+ if success:
299
+ print(f"āœ“ {info}\n")
300
+ else:
301
+ print(f"āŒ {info}\n")
302
+ exit(1)
303
+
304
+ # Record waveforms
305
+ print("[2] Recording waveforms...")
306
+ scope = MSO4000(channel=1)
307
+
308
+ if scope.connect():
309
+ filename = scope.record_to_csv(max_records=5) # Record 5 waveforms
310
+ if filename:
311
+ print(f"\nāœ“ Success! Data saved to: {filename}")
312
+ scope.close()
313
+