Getting code working for DS1054Z on Windows 7 64bit
I have got the code partially working on my DS1054Z. I can see the waveform displayed on the screen but it does not continually update, it only updates when I push the run/stop button on the scope.
Below is what I have done to get it this far on Windows 7 64bit:
I first installed the latest National Instruments VISA runtime:
http://www.ni.com/download/ni-visa-run-time-engine-15.0/5379/en/ NIVISA1500runtime.exe
Next I installed Python 2.7 making sure Python was added to the path (this is so you can run python from the command line from any directory):
https://www.python.org/downloads/release/python-2711/ python-2.7.11.amd64.msi
Next I updated pip and setuptools as well as installing wheel as recommend on: http://python-packaging-user-guide.readthedocs.org/en/latest/installing/#install-pip-setuptools-and-wheel python -m pip install -U pip setuptools pip install wheel
I now updated mock as I could not get any further due to an error (on next step) saying 'Requires setuptools >=17.1 to install properly', the following fixed this issue and seemed to update a couple of other things as well:
pip install mock
Next I installed pyvisa version 1.4 as recommend (the mock update above made this work):
pip install pyvisa==1.4
Next I installed numpy, this initially did not work and showed this 'error: Unable to find vcvarsall.bat', so I installed the Microsoft Visual C++ Compiler for Python 2.7:
https://www.microsoft.com/en-gb/download/details.aspx?id=44266 VCForPython27.msi
Now numpy installed, it took a while as it needed to compile and there was no real progress update so I just left it and after a few minutes it finished and said it had installed.
pip install numpy
Next I downloaded the PyDSA code from Github:
https://github.com/rheslip/PyDSA
The file to run is in the PyDSA directory and is called PyDSA.py
Now the code needed to be altered, I used policeman0077's code to get started (see above comments) and made a few changes as this initially would not work for me:
Below is my working code so far, the changes occur in the 'Main Routine' function after line 612, run the code as follows:
python PyDSA.py
In order for it to work for me I set the trigger on the scope to AC, with the scope stopped I start the program and then after a second or two I press the run/stop button (even though it is now green and scope says triggered), now a wave form appears on the program's display. It does not automatically update but does update once if I press the run/stop button again.
============================================ Main routine ====================================================
def Sweep(): # Read samples and store the data into the arrays global X0L # Left top X value global Y0T # Left top Y value global GRW # Screenwidth global GRH # Screenheight global SIGNAL1 global RUNstatus global SWEEPsingle global SMPfftlist global SMPfftindex global SAMPLErate global SAMPLEsize global SAMPLEdepth global UPDATEspeed global STARTfrequency global STOPfrequency global COLORred global COLORcanvas global COLORyellow global COLORgreen global COLORmagenta
while (True): # Main loop
# RUNstatus = 1 : Open Stream
if (RUNstatus == 1):
if UPDATEspeed < 1:
UPDATEspeed = 1.0
TRACESopened = 1
try:
Get the USB device, e.g. 'USB0::0x1AB1::0x0588::DS1ED141904883'
instruments = visa.get_instruments_list()
usb = filter(lambda x: 'USB' in x, instruments)
if len(usb) != 1:
print 'Bad instrument list', instruments
sys.exit(-1)
scope = visa.instrument(usb[0], timeout=20, chunk_size=1024000) # bigger timeout for long mem
RUNstatus = 2
except: # If error in opening audio stream, show error
RUNstatus = 0
#txt = "Sample rate: " + str(SAMPLErate) + ", try a lower sample rate.\nOr another audio device."
showerror("VISA Error","Cannot open scope")
get metadata
#sample_rate = float(scope.ask(':ACQ:SAMP?'))
#timescale = float(scope.ask(":TIM:SCAL?"))
#timeoffset = float(scope.ask(":TIM:OFFS?"))
#voltscale = float(scope.ask(':CHAN1:SCAL?'))
#voltoffset = float(scope.ask(":CHAN1:OFFS?"))
UpdateScreen() # UpdateScreen() call
# RUNstatus = 2: Reading audio data from soundcard
if (RUNstatus == 2):
# Grab the raw data from channel 1
#try:
Set the scope the way we want it
if SAMPLEdepth == 0:
scope.write(':ACQ:MDEP 12000') # normal memory type
else:
scope.write(':ACQ:MDEP 120000') # long memory type
#scope.write(':CHAN1:COUP DC') # DC coupling
#scope.write(':CHAN1:DISP ON') # Channel 1 on
#scope.write(':CHAN2:DISP ON') # Channel 2 off
#scope.write(':CHAN1:SCAL 1') # Channel 1 vertical scale 1 volts
#scope.write(':CHAN1:OFFS -2') # Channel 1 vertical offset 2 volts
#scope.write(':TIM:SCAL 0.001') # time interval
#scope.write(':TIM:OFFS .05') # Offset time 50 ms
#scope.write(':TRIG:EDGE:SOUR CHAN1') # Edge-trigger from channel 1
#scope.write(':TRIG:EDGE:SWE SING') # Single trigger
#scope.write(':TRIG:EDGE:COUP AC') # trigger coupling
#scope.write(':TRIG:EDGE:SLOP NEG') # Trigger on negative edge
#scope.write(':TRIG:EDGE:LEV 0.01') # Trigger volts
scope.write(":RUN")
#txt = "Trig"
#x = X0L + 250
#y = Y0T+GRH+32
#IDtxt = ca.create_text (x, y, text=txt, anchor=W, fill=COLORyellow)
#root.update() # update screen
while scope.ask(':TRIG:STAT?') != 'STOP':
sleep(0.1)
#sleep(0.1)
# Grab the raw data from channel 1, which will take a few seconds for long buffer mode
scope.write(":STOP")
scope.write(":WAV:SOUR CHAN1")
scope.write(":WAV:MODE RAW")
scope.write(":WAV:FORM BYTE")
scope.write(":WAV:STAR 1")
if SAMPLEdepth == 0:
scope.write(":WAV:STOP 12000")
else:
scope.write(":WAV:STOP 120000")
txt = "->Acquire"
x = X0L + 275
y = Y0T+GRH+32
IDtxt = ca.create_text (x, y, text=txt, anchor=W, fill=COLORgreen)
root.update() # update screen
signals= scope.ask(":WAV:DATA?") #do this first
data_size = len(signals)
SAMPLErate = scope.ask_for_values(':ACQ:SRAT?')[0] #do this second
#print 'Data size:', SAMPLEsize, "Sample rate:", SAMPLErate
sleep(0.1)
convert data from (inverted) bytes to an array of scaled floats
this magic from Matthew Mets
SIGNAL1 = numpy.frombuffer(signals, 'B')
#print SIGNAL1
SIGNAL1 = (SIGNAL1 * -1 + 255) -130 # invert
#print SIGNAL1
SIGNAL1 = SIGNAL1/127.0 # scale 10 +-1, has a slight DC offset
#print SIGNAL1
UpdateAll() # Update Data, trace and screen
if SWEEPsingle == True: # single sweep mode, sweep once then stop
SWEEPsingle = False
RUNstatus = 3
# RUNstatus = 3: Stop
# RUNstatus = 4: Stop and restart
if (RUNstatus == 3) or (RUNstatus == 4):
scope.write(":KEY:FOR")
scope.close()
if RUNstatus == 3:
RUNstatus = 0 # Status is stopped
if RUNstatus == 4:
RUNstatus = 1 # Status is (re)start
UpdateScreen() # UpdateScreen() call
# Update tasks and screens by TKinter
root.update_idletasks()
root.update() # update screens
Thanks for showing the dependencies - I'll post that on the blog.
As for the single sweep problem, I'm not sure how to fix it since your scope is not the same as mine. I experienced similar issues when trying to get PYVISA 1.8 to work. There seem to be subtle differences in the scope firmware and timing. Try extending the sleep(.1) statements and uncomment the one right after the loop that waits for data. I set the delay times short because it worked on my system and I wanted to get the fastest sweep rate possible. Sounds like the code is hanging in the loop waiting for the scope.
Hi,
Thank you very much for getting back to me, it is much appreciated.
I don't suppose you could also post the code under the post you just added for me on your blog - it is a slight modification of the code by policeman0077 (further up the page) as that version had a few missing commands where it does the actual data retrieval from the scope (specifically the 'Grab the raw data from channel 1' part).
I did try to get the code looking neat on Github but it kept insisting on formatting it and highlighting all the comments - after a while fiddling with it I decided to just leave it as it was, copied straight out my working code. The main parts are quite clear even if the comments are a bit on the large side.
I will try adjusting the timing as suggested and let you know how it goes - it is very close to working I think. I have not used Python before yesterday but it is not so different from C++ and PHP which I am used to working with. It is also my first time trying to talk to my scope from my computer...
Anyway, as mentioned above, if you could add the code just after my post I think it would help others get to the same point as me - which is almost working I think. I will paste the code below this email so you have an easy to copy version:
Thanks again for your suggestions and I will let you know how I get on,
Kerr
============================================ Main routine
def Sweep(): # Read samples and store the data into the arrays global X0L # Left top X value global Y0T # Left top Y value global GRW # Screenwidth global GRH # Screenheight global SIGNAL1 global RUNstatus global SWEEPsingle global SMPfftlist global SMPfftindex global SAMPLErate global SAMPLEsize global SAMPLEdepth global UPDATEspeed global STARTfrequency global STOPfrequency global COLORred global COLORcanvas global COLORyellow global COLORgreen global COLORmagenta
while (True): # Main loop
# RUNstatus = 1 : Open Stream
if (RUNstatus == 1):
if UPDATEspeed < 1:
UPDATEspeed = 1.0
TRACESopened = 1
try:
Get the USB device, e.g. 'USB0::0x1AB1::0x0588::DS1ED141904883'
instruments = visa.get_instruments_list()
usb = filter(lambda x: 'USB' in x, instruments)
if len(usb) != 1:
print 'Bad instrument list', instruments
sys.exit(-1)
scope = visa.instrument(usb[0], timeout=20,
chunk_size=1024000) # bigger timeout for long mem
RUNstatus = 2
except: # If error in
opening audio stream, show error RUNstatus = 0 #txt = "Sample rate: " + str(SAMPLErate) + ", try a lower sample rate.\nOr another audio device." showerror("VISA Error","Cannot open scope")
get metadata
#sample_rate = float(scope.ask(':ACQ:SAMP?'))
#timescale = float(scope.ask(":TIM:SCAL?"))
#timeoffset = float(scope.ask(":TIM:OFFS?"))
#voltscale = float(scope.ask(':CHAN1:SCAL?'))
#voltoffset = float(scope.ask(":CHAN1:OFFS?"))
UpdateScreen() #
UpdateScreen() call
# RUNstatus = 2: Reading audio data from soundcard
if (RUNstatus == 2):
# Grab the raw data from channel 1
#try:
Set the scope the way we want it
if SAMPLEdepth == 0:
scope.write(':ACQ:MDEP 12000') # normal memory type
else:
scope.write(':ACQ:MDEP 120000') # long memory type
#scope.write(':CHAN1:COUP DC') # DC coupling
#scope.write(':CHAN1:DISP ON') # Channel 1 on
#scope.write(':CHAN2:DISP ON') # Channel 2 off
#scope.write(':CHAN1:SCAL 1') # Channel 1 vertical scale 1 volts
#scope.write(':CHAN1:OFFS -2') # Channel 1 vertical offset 2
volts #scope.write(':TIM:SCAL 0.001') # time interval #scope.write(':TIM:OFFS .05') # Offset time 50 ms
#scope.write(':TRIG:EDGE:SOUR CHAN1') # Edge-trigger from
channel 1 #scope.write(':TRIG:EDGE:SWE SING') # Single trigger #scope.write(':TRIG:EDGE:COUP AC') # trigger coupling #scope.write(':TRIG:EDGE:SLOP NEG') # Trigger on negative edge #scope.write(':TRIG:EDGE:LEV 0.01') # Trigger volts scope.write(":RUN")
#txt = "Trig"
#x = X0L + 250
#y = Y0T+GRH+32
#IDtxt = ca.create_text (x, y, text=txt, anchor=W,
fill=COLORyellow) #root.update() # update screen
while scope.ask(':TRIG:STAT?') != 'STOP':
sleep(0.1)
#sleep(0.1)
# Grab the raw data from channel 1, which will take a few seconds for
long buffer mode
scope.write(":STOP")
scope.write(":WAV:SOUR CHAN1")
scope.write(":WAV:MODE RAW")
scope.write(":WAV:FORM BYTE")
scope.write(":WAV:STAR 1")
if SAMPLEdepth == 0:
scope.write(":WAV:STOP 12000")
else:
scope.write(":WAV:STOP 120000")
txt = "->Acquire"
x = X0L + 275
y = Y0T+GRH+32
IDtxt = ca.create_text (x, y, text=txt, anchor=W,
fill=COLORgreen) root.update() # update screen
signals= scope.ask(":WAV:DATA?") #do this first
data_size = len(signals)
SAMPLErate = scope.ask_for_values(':ACQ:SRAT?')[0] #do this
second #print 'Data size:', SAMPLEsize, "Sample rate:", SAMPLErate
sleep(0.1)
convert data from (inverted) bytes to an array of scaled floats
this magic from Matthew Mets
SIGNAL1 = numpy.frombuffer(signals, 'B')
#print SIGNAL1
SIGNAL1 = (SIGNAL1 * -1 + 255) -130 # invert
#print SIGNAL1
SIGNAL1 = SIGNAL1/127.0 # scale 10 +-1, has a slight DC offset
#print SIGNAL1
UpdateAll() # Update Data,
trace and screen
if SWEEPsingle == True: # single sweep mode, sweep once then
stop SWEEPsingle = False RUNstatus = 3
# RUNstatus = 3: Stop
# RUNstatus = 4: Stop and restart
if (RUNstatus == 3) or (RUNstatus == 4):
scope.write(":KEY:FOR")
scope.close()
if RUNstatus == 3:
RUNstatus = 0 # Status is
stopped if RUNstatus == 4: RUNstatus = 1 # Status is (re)start UpdateScreen() # UpdateScreen() call
# Update tasks and screens by TKinter
root.update_idletasks()
root.update() # update screens
That's great, thanks.
I am not sure why I am not able to post properly.
I will have a look at the code and see if I can figure anything out - I will keep you updated.
Thanks again,
Kerr
On 31 January 2016 at 17:52, rheslip [email protected] wrote:
Kerr - I posted your code on the blog. Once you get it sweeping could you send me a zip of it ? I can put in in github as a 1054Z version which will make it a lot easier for other people to use it. Just send it to me directly [email protected] Thanks, Rich
Date: Sun, 31 Jan 2016 08:35:51 -0800 From: [email protected] To: [email protected] CC: [email protected] Subject: Re: [PyDSA] Getting code working for DS1054Z on Windows 7 64bit (#1)
Hi,
Thank you very much for getting back to me, it is much appreciated.
I don't suppose you could also post the code under the post you just added
for me on your blog - it is a slight modification of the code by
policeman0077 (further up the page) as that version had a few missing
commands where it does the actual data retrieval from the scope
(specifically the 'Grab the raw data from channel 1' part).
I did try to get the code looking neat on Github but it kept insisting on
formatting it and highlighting all the comments - after a while fiddling
with it I decided to just leave it as it was, copied straight out my
working code. The main parts are quite clear even if the comments are a
bit on the large side.
I will try adjusting the timing as suggested and let you know how it goes -
it is very close to working I think. I have not used Python before
yesterday but it is not so different from C++ and PHP which I am used to
working with. It is also my first time trying to talk to my scope from my
computer...
Anyway, as mentioned above, if you could add the code just after my post I
think it would help others get to the same point as me - which is almost
working I think. I will paste the code below this email so you have an
easy to copy version:
Thanks again for your suggestions and I will let you know how I get on,
Kerr
============================================ Main routine
def Sweep(): # Read samples and store the data into the arrays
global X0L # Left top X value
global Y0T # Left top Y value
global GRW # Screenwidth
global GRH # Screenheight
global SIGNAL1
global RUNstatus
global SWEEPsingle
global SMPfftlist
global SMPfftindex
global SAMPLErate
global SAMPLEsize
global SAMPLEdepth
global UPDATEspeed
global STARTfrequency
global STOPfrequency
global COLORred
global COLORcanvas
global COLORyellow
global COLORgreen
global COLORmagenta
while (True): # Main loop
RUNstatus = 1 : Open Stream
if (RUNstatus == 1):
if UPDATEspeed < 1:
UPDATEspeed = 1.0
TRACESopened = 1
try:
Get the USB device, e.g. 'USB0::0x1AB1::0x0588::DS1ED141904883'
instruments = visa.get_instruments_list()
usb = filter(lambda x: 'USB' in x, instruments)
if len(usb) != 1:
print 'Bad instrument list', instruments
sys.exit(-1)
scope = visa.instrument(usb[0], timeout=20,
chunk_size=1024000) # bigger timeout for long mem
RUNstatus = 2
except: # If error in
opening audio stream, show error
RUNstatus = 0
#txt = "Sample rate: " + str(SAMPLErate) + ", try a lower
sample rate.\nOr another audio device."
showerror("VISA Error","Cannot open scope")
get metadata
#sample_rate = float(scope.ask(':ACQ:SAMP?'))
#timescale = float(scope.ask(":TIM:SCAL?"))
#timeoffset = float(scope.ask(":TIM:OFFS?"))
#voltscale = float(scope.ask(':CHAN1:SCAL?'))
#voltoffset = float(scope.ask(":CHAN1:OFFS?"))
UpdateScreen() #
UpdateScreen() call
RUNstatus = 2: Reading audio data from soundcard
if (RUNstatus == 2):
Grab the raw data from channel 1
#try:
Set the scope the way we want it
if SAMPLEdepth == 0:
scope.write(':ACQ:MDEP 12000') # normal memory type
else:
scope.write(':ACQ:MDEP 120000') # long memory type
#scope.write(':CHAN1:COUP DC') # DC coupling
#scope.write(':CHAN1:DISP ON') # Channel 1 on
#scope.write(':CHAN2:DISP ON') # Channel 2 off
#scope.write(':CHAN1:SCAL 1') # Channel 1 vertical scale 1 volts
#scope.write(':CHAN1:OFFS -2') # Channel 1 vertical offset 2
volts
#scope.write(':TIM:SCAL 0.001') # time interval
#scope.write(':TIM:OFFS .05') # Offset time 50 ms
#scope.write(':TRIG:EDGE:SOUR CHAN1') # Edge-trigger from
channel 1
#scope.write(':TRIG:EDGE:SWE SING') # Single trigger
#scope.write(':TRIG:EDGE:COUP AC') # trigger coupling
#scope.write(':TRIG:EDGE:SLOP NEG') # Trigger on negative edge
#scope.write(':TRIG:EDGE:LEV 0.01') # Trigger volts
scope.write(":RUN")
#txt = "Trig"
#x = X0L + 250
#y = Y0T+GRH+32
#IDtxt = ca.create_text (x, y, text=txt, anchor=W,
fill=COLORyellow)
#root.update() # update screen
while scope.ask(':TRIG:STAT?') != 'STOP':
sleep(0.1)
#sleep(0.1)
Grab the raw data from channel 1, which will take a few seconds for
long buffer mode
scope.write(":STOP")
scope.write(":WAV:SOUR CHAN1")
scope.write(":WAV:MODE RAW")
scope.write(":WAV:FORM BYTE")
scope.write(":WAV:STAR 1")
if SAMPLEdepth == 0:
scope.write(":WAV:STOP 12000")
else:
scope.write(":WAV:STOP 120000")
txt = "->Acquire"
x = X0L + 275
y = Y0T+GRH+32
IDtxt = ca.create_text (x, y, text=txt, anchor=W,
fill=COLORgreen)
root.update() # update screen
signals= scope.ask(":WAV:DATA?") #do this first
data_size = len(signals)
SAMPLErate = scope.ask_for_values(':ACQ:SRAT?')[0] #do this
second
#print 'Data size:', SAMPLEsize, "Sample rate:", SAMPLErate
sleep(0.1)
convert data from (inverted) bytes to an array of scaled floats
this magic from Matthew Mets
SIGNAL1 = numpy.frombuffer(signals, 'B')
#print SIGNAL1
SIGNAL1 = (SIGNAL1 * -1 + 255) -130 # invert
#print SIGNAL1
SIGNAL1 = SIGNAL1/127.0 # scale 10 +-1, has a slight DC offset
#print SIGNAL1
UpdateAll() # Update Data,
trace and screen
if SWEEPsingle == True: # single sweep mode, sweep once then
stop
SWEEPsingle = False
RUNstatus = 3
RUNstatus = 3: Stop
RUNstatus = 4: Stop and restart
if (RUNstatus == 3) or (RUNstatus == 4):
scope.write(":KEY:FOR")
scope.close()
if RUNstatus == 3:
RUNstatus = 0 # Status is
stopped
if RUNstatus == 4:
RUNstatus = 1 # Status is
(re)start
UpdateScreen() #
UpdateScreen() call
Update tasks and screens by TKinter
root.update_idletasks()
root.update() # update screens
— Reply to this email directly or view it on GitHub.
— Reply to this email directly or view it on GitHub https://github.com/rheslip/PyDSA/issues/1#issuecomment-177563896.
I have now got the code working, the issue was that the code was stuck in a loop on line 605. This while loop was waiting for a 'STOP' message from the scope but it was not getting it - my scope sends 'TD' initially then mainly 'AUTO' but sometimes another 'TD' every so often.
I also removed the sleep commands from this main routine function and the program still works, I get an update rate of 2 to 3 screen updates a second.
I have attached my current working file to this message.
Thanks for the work here. From my side I took up this code, added:
- larger sample depths with corresponding fft handling sizes (this requires reading the data of a sample in multiple batches, as 250K is the max per VISA level read)
- adaptive display of the frequency (not everything in MHz, sometimes kHz is needed also...)
- better stop/run/single shot handling (to ensure we have fresh data at every run)
- adapt to the latest version of the VISA software (which is a PAIN to install on OS/X by the way)
- removed some bugs (one must discard the sample header from the FFT data for example)
I did all this in an effort to reduce the noise level, and am mostly interested in audio. I know the scope's ADC has a rather low resolution for audio, but I decided to give it a go anyway, hoping to compensate with a large sample size.
But: For some reason I still have much better noise reduction when averaging over multiple passes than just providing one (huge) sample set. Even when the sample set is 10+ times bigger than the all the data one uses when averaging. And yes, I did adapt the FFT sample buffer size to the nearest-lower-power-of-2 value suitable for the scope's sample size. Now the last time I did some serious work on FFT theory is more than 30 years ago, so I may be a bit rusty, but this does not sound good to me.
And on top of that, the damn thing is LOUD. For every data read (and when reading large data sets one has a lot of that) the scope beeps. (I may be able to switch that beep off, haven't looked). And it is SLOW (the scope, python and the USB transfer are all partly responsible for that). Not ideal.
So in case anyone is interested, I can share my work, and I may even look into it some more, but my conclusion is that a dedicated FFT machine or some high resolution sampling card with good PC or Mac software would be more suited for serious work.
For audio you should take a look at PA2OHH's code on which PyDSA is based: http://www.qsl.net/pa2ohh/11sa.htm Regards, Rich
Date: Sun, 6 Mar 2016 12:29:09 -0800 From: [email protected] To: [email protected] CC: [email protected] Subject: Re: [PyDSA] Getting code working for DS1054Z on Windows 7 64bit (#1)
Thanks for the work here. From my side I took up this code, added:
larger sample depths with corresponding fft handling sizes (this requires reading the data of a sample in multiple batches, as 250K is the max per VISA level read) adaptive display of the frequency (not everything in MHz, sometimes kHz is needed also...) better stop/run/single shot handling (to ensure we have fresh data at every run) adapt to the latest version of the VISA software (which is a PAIN to install on OS/X by the way) removed some bugs (one must discard the sample header from the FFT data for example)
I did all this in an effort to reduce the noise level, and am mostly interested in audio. I know the scope's ADC has a rather low resolution for audio, but I decided to give it a go anyway, hoping to compensate with a large sample size.
But: For some reason I still have much better noise reduction when averaging over multiple passes than just providing one (huge) sample set. Even when the sample set is 10+ times bigger than the all the data one uses when averaging. And yes, I did adapt the FFT sample buffer size to the nearest-lower-power-of-2 value suitable for the scope's sample size. Now the last time I did some serious work on FFT theory is more than 30 years ago, so I may be a bit rusty, but this does not sound good to me.
And on top of that, the damn thing is LOUD. For every data read (and when reading large data sets one has a lot of that) the scope beeps. (I may be able to switch that beep off, haven't looked). And it is SLOW (the scope, python and the USB transfer are all partly responsible for that). Not ideal.
So in case anyone is interested, I can share my work, and I may even look into it some more, but my conclusion is that a dedicated FFT machine or some high resolution sampling card with good PC or Mac software would be more suited for serious work.
— Reply to this email directly or view it on GitHub.
@hb020
And on top of that, the damn thing is LOUD. For every data read (and when reading large data sets one has a lot of that) the scope beeps.
Try adding in a:
scope.write(":BEEP:ENAB OFF")