Browse Source

Begin a major refactor

Now we have real command-line argument parsing, rather than hacking it
together with sys.argv.  Much better.  Also, we use an enum for the file
types rather than hard-coding the extension strings all over the place.
Clara Hobbs 5 years ago
parent
commit
f57730f6b4
1 changed files with 216 additions and 205 deletions
  1. 216
    205
      OscScreenGrabLAN.py

+ 216
- 205
OscScreenGrabLAN.py View File

@@ -1,12 +1,25 @@
1 1
 #!/usr/bin/env python3
2
+"""Take screen captures from DS1000Z-series oscilloscopes
2 3
 
3
-from telnetlib_receive_all import Telnet
4
-from Rigol_functions import *
5
-import time
6
-import sys
4
+This program captures either the waveform or the whole screen of a Rigol
5
+DS1000Z series oscilloscope, then saves it on the computer as a CSV, PNG
6
+or BMP file.
7
+
8
+The program uses the LXI protocol, so the computer must have a LAN
9
+connection with the oscilloscope.
10
+"""
11
+
12
+from enum import Enum, auto
13
+import argparse
14
+import logging
7 15
 import os
8 16
 import platform
9
-import logging
17
+import subprocess
18
+import sys
19
+import time
20
+
21
+from Rigol_functions import *
22
+from telnetlib_receive_all import Telnet
10 23
 
11 24
 __version__ = 'v1.1.0'
12 25
 # Added TMC Blockheader decoding
@@ -54,7 +67,6 @@ log_running_python_versions()
54 67
 
55 68
 # Update the next lines for your own default settings:
56 69
 path_to_save = "captures/"
57
-save_format = "PNG"
58 70
 IP_DS1104Z = "192.168.1.3"
59 71
 
60 72
 # Rigol/LXI specific constants
@@ -67,221 +79,220 @@ company = 0
67 79
 model = 1
68 80
 serial = 2
69 81
 
70
-# Check command line parameters
71
-script_name = os.path.basename(sys.argv[0])
72
-
73
-
74
-def print_help():
75
-    print ()
76
-    print ("Usage:")
77
-    print ("    " + "python " + script_name + " png|bmp|csv [oscilloscope_IP [save_path]]")
78
-    print ()
79
-    print ("Usage examples:")
80
-    print ("    " + "python " + script_name + " png")
81
-    print ("    " + "python " + script_name + " csv 192.168.1.3")
82
-    print ()
83
-    print ("The following usage cases are not yet implemented:")
84
-    print ("    " + "python " + script_name + " bmp 192.168.1.3 my_place_for_captures")
85
-    print ()
86
-    print ("This program captures either the waveform or the whole screen")
87
-    print ("    of a Rigol DS1000Z series oscilloscope, then save it on the computer")
88
-    print ("    as a CSV, PNG or BMP file with a timestamp in the file name.")
89
-    print ()
90
-    print ("    The program is using LXI protocol, so the computer")
91
-    print ("    must have LAN connection with the oscilloscope.")
92
-    print ("    USB and/or GPIB connections are not used by this software.")
93
-    print ()
94
-    print ("    No VISA, IVI or Rigol drivers are needed.")
95
-    print ()
96 82
 
97 83
 # Read/verify file type
98
-if len(sys.argv) <= 1:
99
-    print_help()
100
-    sys.exit("Warning - wrong command line parameters.")
101
-elif sys.argv[1].lower() not in ["png", "bmp", "csv"]:
102
-    print_help()
103
-    print ("This file type is not supported: ", sys.argv[1])
104
-    sys.exit("ERROR")
84
+class FileType(Enum):
85
+    png = auto()
86
+    bmp = auto()
87
+    csv = auto()
105 88
 
106
-file_format = sys.argv[1].lower()
107
-
108
-# Read IP
109
-if len(sys.argv) > 1:
110
-    IP_DS1104Z = sys.argv[2]
111 89
 
112 90
 # Check network response (ping)
113
-if platform.system() == "Windows":
114
-    response = os.system("ping -n 1 " + IP_DS1104Z + " > nul")
115
-else:
116
-    response = os.system("ping -c 1 " + IP_DS1104Z + " > /dev/null")
117
-
118
-if response != 0:
119
-    print ()
120
-    print ("WARNING! No response pinging " + IP_DS1104Z)
121
-    print ("Check network cables and settings.")
122
-    print ("You should be able to ping the oscilloscope.")
123
-
124
-# Open a modified telnet session
125
-# The default telnetlib drops 0x00 characters,
126
-#   so a modified library 'telnetlib_receive_all' is used instead
127
-tn = Telnet(IP_DS1104Z, port)
128
-instrument_id = command(tn, "*IDN?").decode()    # ask for instrument ID
129
-
130
-# Check if instrument is set to accept LAN commands
131
-if instrument_id == "command error":
132
-    print ("Instrument reply:", instrument_id)
133
-    print ("Check the oscilloscope settings.")
134
-    print ("Utility -> IO Setting -> RemoteIO -> LAN must be ON")
135
-    sys.exit("ERROR")
136
-
137
-# Check if instrument is indeed a Rigol DS1000Z series
138
-id_fields = instrument_id.split(",")
139
-if (id_fields[company] != "RIGOL TECHNOLOGIES") or \
140
-        (id_fields[model][:3] != "DS1") or (id_fields[model][-1] != "Z"):
141
-    print ("Found instrument model '{}' from '{}'".format(id_fields[model], id_fields[company]))
142
-    print ("WARNING: No Rigol from series DS1000Z found at", IP_DS1104Z)
143
-    print ()
144
-    typed = raw_input("ARE YOU SURE YOU WANT TO CONTINUE? (No/Yes):")
145
-    if typed != 'Yes':
146
-        sys.exit('Nothing done. Bye!')
147
-
148
-print ("Instrument ID:", instrument_id)
149
-
150
-# Prepare filename as C:\MODEL_SERIAL_YYYY-MM-DD_HH.MM.SS
151
-timestamp = time.strftime("%Y-%m-%d_%H.%M.%S", time.localtime())
152
-if len(sys.argv) > 3:
153
-    filename = sys.argv[3]
154
-else:
155
-    filename = path_to_save + id_fields[model] + "_" + id_fields[serial] + "_" + timestamp + "." + file_format
156
-
157
-if file_format in ["png", "bmp"]:
158
-    # Ask for an oscilloscope display print screen
159
-    print ("Receiving screen capture...")
160
-
161
-    if file_format == "png":
162
-    	buff = command(tn, ":DISP:DATA? ON,OFF,PNG")
91
+def test_ping(hostname):
92
+    """Ping hostname once"""
93
+    if platform.system() == "Windows":
94
+        command = ['ping', '-n', '1', hostname]
163 95
     else:
164
-    	buff = command(tn, ":DISP:DATA? ON,OFF,BMP8")
165
-
166
-    expectedBuffLen = expected_buff_bytes(buff)
167
-    # Just in case the transfer did not complete in the expected time, read the remaining 'buff' chunks
168
-    while len(buff) < expectedBuffLen:
169
-        logging.warning("Received LESS data then expected! (" +
170
-                        str(len(buff)) + " out of " + str(expectedBuffLen) + " expected 'buff' bytes.)")
171
-        tmp = tn.read_until(b"\n", smallWait)
172
-        if len(tmp) == 0:
173
-            break
174
-        buff += tmp
175
-        logging.warning(str(len(tmp)) + " leftover bytes added to 'buff'.")
176
-
177
-    if len(buff) < expectedBuffLen:
178
-        logging.error("After reading all data chunks, 'buff' is still shorter then expected! (" +
179
-                      str(len(buff)) + " out of " + str(expectedBuffLen) + " expected 'buff' bytes.)")
96
+        command = ['ping', '-c', '1', hostname]
97
+    completed = subprocess.run(command, stdout=subprocess.DEVNULL,
98
+                   stderr=subprocess.DEVNULL)
99
+
100
+    if completed.returncode != 0:
101
+        print()
102
+        print("WARNING! No response pinging", hostname)
103
+        print("Check network cables and settings.")
104
+        print("You should be able to ping the oscilloscope.")
105
+
106
+def run(hostname, filename, filetype):
107
+    test_ping(hostname)
108
+
109
+    # Open a modified telnet session
110
+    # The default telnetlib drops 0x00 characters,
111
+    #   so a modified library 'telnetlib_receive_all' is used instead
112
+    tn = Telnet(hostname, port)
113
+    instrument_id = command(tn, "*IDN?").decode()    # ask for instrument ID
114
+
115
+    # Check if instrument is set to accept LAN commands
116
+    if instrument_id == "command error":
117
+        print ("Instrument reply:", instrument_id)
118
+        print ("Check the oscilloscope settings.")
119
+        print ("Utility -> IO Setting -> RemoteIO -> LAN must be ON")
180 120
         sys.exit("ERROR")
181 121
 
182
-    # Strip TMC Blockheader and keep only the data
183
-    tmcHeaderLen = tmc_header_bytes(buff)
184
-    expectedDataLen = expected_data_bytes(buff)
185
-    buff = buff[tmcHeaderLen: tmcHeaderLen+expectedDataLen]
186
-
187
-    # Write raw data to file
188
-    with open(filename, 'wb') as f:
189
-    	f.write(buff)
190
-    print('Saved raw data to {}'.format(filename))
191
-
192
-# TODO: Change WAV:FORM from ASC to BYTE
193
-elif file_format == "csv":
194
-    # Put the scope in STOP mode - for the moment, deal with it by manually stopping the scope
195
-    # TODO: Add command line switch and code logic for 1200 vs ALL memory data points
196
-    # tn.write("stop")
197
-    # response = tn.read_until("\n", 1)
198
-
199
-    # Scan for displayed channels
200
-    chanList = []
201
-    for channel in ["CHAN1", "CHAN2", "CHAN3", "CHAN4", "MATH"]:
202
-        response = command(tn, ":" + channel + ":DISP?")
203
-
204
-        # If channel is active
205
-        if response == '1\n':
206
-            chanList += [channel]
207
-
208
-    # the meaning of 'max' is   - will read only the displayed data when the scope is in RUN mode,
209
-    #                             or when the MATH channel is selected
210
-    #                           - will read all the acquired data points when the scope is in STOP mode
211
-    # TODO: Change mode to MAX
212
-    # TODO: Add command line switch for MAX/NORM
213
-    command(tn, ":WAV:MODE NORM")
214
-    command(tn, ":WAV:STAR 0")
215
-    command(tn, ":WAV:MODE NORM")
216
-
217
-    csv_buff = ""
218
-
219
-    # for each active channel
220
-    for channel in chanList:
122
+    # Check if instrument is indeed a Rigol DS1000Z series
123
+    id_fields = instrument_id.split(",")
124
+    if (id_fields[company] != "RIGOL TECHNOLOGIES") or \
125
+            (id_fields[model][:3] != "DS1") or (id_fields[model][-1] != "Z"):
126
+        print ("Found instrument model '{}' from '{}'".format(id_fields[model], id_fields[company]))
127
+        print ("WARNING: No Rigol from series DS1000Z found at", hostname)
221 128
         print ()
129
+        typed = raw_input("ARE YOU SURE YOU WANT TO CONTINUE? (No/Yes):")
130
+        if typed != 'Yes':
131
+            sys.exit('Nothing done. Bye!')
222 132
 
223
-        # Set WAVE parameters
224
-        command(tn, ":WAV:SOUR " + channel)
225
-        command(tn, ":WAV:FORM ASC")
133
+    print ("Instrument ID:", instrument_id)
226 134
 
227
-        # MATH channel does not allow START and STOP to be set. They are always 0 and 1200
228
-        if channel != "MATH":
229
-            command(tn, ":WAV:STAR 1")
230
-            command(tn, ":WAV:STOP 1200")
135
+    # Prepare filename as C:\MODEL_SERIAL_YYYY-MM-DD_HH.MM.SS
136
+    timestamp = time.strftime("%Y-%m-%d_%H.%M.%S", time.localtime())
137
+    if filename is None:
138
+        filename = "{}{}_{}_{}.{}".format(path_to_save, id_fields[model],
139
+                                          id_fields[serial], timestamp,
140
+                                          filetype.name)
231 141
 
232
-        buff = ""
233
-        print ("Data from channel '" + str(channel) + "', points " + str(1) + "-" + str(1200) + ": Receiving...")
234
-        buffChunk = command(tn, ":WAV:DATA?")
142
+    if filetype in {FileType.png, FileType.bmp}:
143
+        # Ask for an oscilloscope display print screen
144
+        print ("Receiving screen capture...")
235 145
 
236
-        # Just in case the transfer did not complete in the expected time
237
-        while buffChunk[-1] != "\n":
238
-            logging.warning("The data transfer did not complete in the expected time of " +
239
-                            str(smallWait) + " second(s).")
146
+        if filetype is FileType.png:
147
+            buff = command(tn, ":DISP:DATA? ON,OFF,PNG")
148
+        else:
149
+            buff = command(tn, ":DISP:DATA? ON,OFF,BMP8")
240 150
 
151
+        expectedBuffLen = expected_buff_bytes(buff)
152
+        # Just in case the transfer did not complete in the expected time, read the remaining 'buff' chunks
153
+        while len(buff) < expectedBuffLen:
154
+            logging.warning("Received LESS data then expected! (" +
155
+                            str(len(buff)) + " out of " + str(expectedBuffLen) + " expected 'buff' bytes.)")
241 156
             tmp = tn.read_until(b"\n", smallWait)
242 157
             if len(tmp) == 0:
243 158
                 break
244
-            buffChunk += tmp
245
-            logging.warning(str(len(tmp)) + " leftover bytes added to 'buff_chunks'.")
246
-
247
-        # Append data chunks
248
-        # Strip TMC Blockheader and terminator bytes
249
-        buff += buffChunk[tmc_header_bytes(buffChunk):-1] + ","
250
-
251
-        # Strip the last \n char
252
-        buff = buff[:-1]
253
-
254
-        # Process data
255
-        buff_list = buff.split(",")
256
-        buff_rows = len(buff_list)
257
-
258
-        # Put read data into csv_buff
259
-        csv_buff_list = csv_buff.split(os.linesep)
260
-        csv_rows = len(csv_buff_list)
261
-
262
-        current_row = 0
263
-        if csv_buff == "":
264
-            csv_first_column = True
265
-            csv_buff = str(channel) + os.linesep
266
-        else:
267
-            csv_first_column = False
268
-            csv_buff = str(csv_buff_list[current_row]) + "," + str(channel) + os.linesep
269
-
270
-        for point in buff_list:
271
-            current_row += 1
272
-            if csv_first_column:
273
-                csv_buff += str(point) + os.linesep
159
+            buff += tmp
160
+            logging.warning(str(len(tmp)) + " leftover bytes added to 'buff'.")
161
+
162
+        if len(buff) < expectedBuffLen:
163
+            logging.error("After reading all data chunks, 'buff' is still shorter then expected! (" +
164
+                          str(len(buff)) + " out of " + str(expectedBuffLen) + " expected 'buff' bytes.)")
165
+            sys.exit("ERROR")
166
+
167
+        # Strip TMC Blockheader and keep only the data
168
+        tmcHeaderLen = tmc_header_bytes(buff)
169
+        expectedDataLen = expected_data_bytes(buff)
170
+        buff = buff[tmcHeaderLen: tmcHeaderLen+expectedDataLen]
171
+
172
+        # Write raw data to file
173
+        with open(filename, 'wb') as f:
174
+            f.write(buff)
175
+        print('Saved raw data to {}'.format(filename))
176
+
177
+    # TODO: Change WAV:FORM from ASC to BYTE
178
+    elif filetype is FileType.csv:
179
+        # Put the scope in STOP mode - for the moment, deal with it by manually stopping the scope
180
+        # TODO: Add command line switch and code logic for 1200 vs ALL memory data points
181
+        # tn.write("stop")
182
+        # response = tn.read_until("\n", 1)
183
+
184
+        # Scan for displayed channels
185
+        chanList = []
186
+        for channel in ["CHAN1", "CHAN2", "CHAN3", "CHAN4", "MATH"]:
187
+            response = command(tn, ":" + channel + ":DISP?")
188
+
189
+            # If channel is active
190
+            if response == '1\n':
191
+                chanList += [channel]
192
+
193
+        # the meaning of 'max' is   - will read only the displayed data when the scope is in RUN mode,
194
+        #                             or when the MATH channel is selected
195
+        #                           - will read all the acquired data points when the scope is in STOP mode
196
+        # TODO: Change mode to MAX
197
+        # TODO: Add command line switch for MAX/NORM
198
+        command(tn, ":WAV:MODE NORM")
199
+        command(tn, ":WAV:STAR 0")
200
+        command(tn, ":WAV:MODE NORM")
201
+
202
+        csv_buff = ""
203
+
204
+        # for each active channel
205
+        for channel in chanList:
206
+            print ()
207
+
208
+            # Set WAVE parameters
209
+            command(tn, ":WAV:SOUR " + channel)
210
+            command(tn, ":WAV:FORM ASC")
211
+
212
+            # MATH channel does not allow START and STOP to be set. They are always 0 and 1200
213
+            if channel != "MATH":
214
+                command(tn, ":WAV:STAR 1")
215
+                command(tn, ":WAV:STOP 1200")
216
+
217
+            buff = ""
218
+            print ("Data from channel '" + str(channel) + "', points " + str(1) + "-" + str(1200) + ": Receiving...")
219
+            buffChunk = command(tn, ":WAV:DATA?")
220
+
221
+            # Just in case the transfer did not complete in the expected time
222
+            while buffChunk[-1] != "\n":
223
+                logging.warning("The data transfer did not complete in the expected time of " +
224
+                                str(smallWait) + " second(s).")
225
+
226
+                tmp = tn.read_until(b"\n", smallWait)
227
+                if len(tmp) == 0:
228
+                    break
229
+                buffChunk += tmp
230
+                logging.warning(str(len(tmp)) + " leftover bytes added to 'buff_chunks'.")
231
+
232
+            # Append data chunks
233
+            # Strip TMC Blockheader and terminator bytes
234
+            buff += buffChunk[tmc_header_bytes(buffChunk):-1] + ","
235
+
236
+            # Strip the last \n char
237
+            buff = buff[:-1]
238
+
239
+            # Process data
240
+            buff_list = buff.split(",")
241
+            buff_rows = len(buff_list)
242
+
243
+            # Put read data into csv_buff
244
+            csv_buff_list = csv_buff.split(os.linesep)
245
+            csv_rows = len(csv_buff_list)
246
+
247
+            current_row = 0
248
+            if csv_buff == "":
249
+                csv_first_column = True
250
+                csv_buff = str(channel) + os.linesep
274 251
             else:
275
-                if current_row < csv_rows:
276
-                    csv_buff += str(csv_buff_list[current_row]) + "," + str(point) + os.linesep
277
-                else:
278
-                    csv_buff += "," + str(point) + os.linesep
252
+                csv_first_column = False
253
+                csv_buff = str(csv_buff_list[current_row]) + "," + str(channel) + os.linesep
279 254
 
280
-    # Save data as CSV
281
-    scr_file = open(filename, "wb")
282
-    scr_file.write(csv_buff)
283
-    scr_file.close()
284
-
285
-    print ("Saved file:", "'" + filename + "'")
286
-
287
-tn.close()
255
+            for point in buff_list:
256
+                current_row += 1
257
+                if csv_first_column:
258
+                    csv_buff += str(point) + os.linesep
259
+                else:
260
+                    if current_row < csv_rows:
261
+                        csv_buff += str(csv_buff_list[current_row]) + "," + str(point) + os.linesep
262
+                    else:
263
+                        csv_buff += "," + str(point) + os.linesep
264
+
265
+        # Save data as CSV
266
+        scr_file = open(filename, "wb")
267
+        scr_file.write(csv_buff)
268
+        scr_file.close()
269
+
270
+        print ("Saved file:", "'" + filename + "'")
271
+
272
+    tn.close()
273
+
274
+if __name__ == "__main__":
275
+    parser = argparse.ArgumentParser(description="Take screen captures from"
276
+            " DS1000Z-series oscilloscopes")
277
+    parser.add_argument("-t", "--type",
278
+            choices=FileType.__members__,
279
+            help="Optional type of file to save")
280
+    parser.add_argument("hostname",
281
+            help="Hostname or IP address of the oscilloscope")
282
+    parser.add_argument("filename", nargs="?",
283
+            help="Optional name of output file")
284
+
285
+    args = parser.parse_args()
286
+
287
+    # If no type is specified, auto-detect from the filename
288
+    if args.type is None:
289
+        if args.filename is None:
290
+            parser.error("Either a file type or a filename must be specified")
291
+        args.type = os.path.splitext(args.filename)[1][1:]
292
+
293
+    try:
294
+        args.type = FileType[args.type]
295
+    except KeyError:
296
+        parser.error("Unknown file type: {}".format(args.type))
297
+
298
+    run(args.hostname, args.filename, args.type)

Loading…
Cancel
Save