Repository: microsoft/Qcodes

Issue:
Driver/AWG5208 minor issues
The current Tektronix 5208 driver has a few quirks.

- The validator for channel amplitudes is wrong
- A corrupt `.seqx` file is **not** overwritten by `sendSEQXFile` leading to a lot of hair pulling ("but this code generating a file worked a minute ago!")
- A sequence loaded into memory is not overwritten when loaded a second time even though the `.seqx` file changed
- The marker stopped value is missing
- The marker range is wrong, should be +/- 1.75 V



Discussion:
Also, there should be a run state (`AWGCONTROL:RSTATE?`) parameter.
@WilliamHPNielsen would it be possible to add a Wait_For_Run function (maybe with a better name) that waits until the AWG is running:
```
def Wait_For_Run(self):
    while self.ask('AWGCONTROL:RSTATE?') == '0':
        time.sleep(0.1)
```
@ThorvaldLarsen roger that. I'll do a little bit of testing to see how far one can get with `*OPC?`, but it seems like a good and useful function (although I'd add a timeout).

Resolving patch:
diff --git a/qcodes/instrument_drivers/tektronix/AWG70000A.py b/qcodes/instrument_drivers/tektronix/AWG70000A.py
--- a/qcodes/instrument_drivers/tektronix/AWG70000A.py
+++ b/qcodes/instrument_drivers/tektronix/AWG70000A.py
@@ -1,4 +1,5 @@
 import datetime as dt
+import time
 import struct
 import io
 import zipfile as zf
@@ -38,6 +39,8 @@ def _parse_string_response(input_str: str) -> str:
 #
 # MODEL DEPENDENT SETTINGS
 #
+# TODO: it seems that a lot of settings differ between models
+# perhaps these dicts should be merged to one
 
 _fg_path_val_map = {'5208': {'DC High BW': "DCHB",
                              'DC High Voltage': "DCHV",
@@ -59,6 +62,19 @@ def _parse_string_response(input_str: str) -> str:
                      '70001A': [8, 9, 10],
                      '70002A': [8, 9, 10]}
 
+# channel amplitudes
+_chan_amps = {'70001A': 0.5,
+              '70002A': 0.5,
+              '5208': 1.5}
+
+# marker ranges
+_marker_high = {'70001A': (-1.4, 1.4),
+                '70002A': (-1.4, 1.4),
+                '5208': (-0.5, 1.75)}
+_marker_low = {'70001A': (-1.4, 1.4),
+               '70002A': (-1.4, 1.4),
+               '5208': (-0.3, 1.55)}
+
 
 class SRValidator(Validator):
     """
@@ -136,7 +152,7 @@ def __init__(self,  parent: Instrument, name: str, channel: int) -> None:
                            get_cmd='FGEN:CHANnel{}:AMPLitude?'.format(channel),
                            set_cmd='FGEN:CHANnel{}:AMPLitude {{}}'.format(channel),
                            unit='V',
-                           vals=vals.Numbers(0, 0.5),
+                           vals=vals.Numbers(0, _chan_amps[self.model]),
                            get_parser=float)
 
         self.add_parameter('fgen_offset',
@@ -218,7 +234,7 @@ def __init__(self,  parent: Instrument, name: str, channel: int) -> None:
             get_cmd='SOURce{}:VOLTage?'.format(channel),
             unit='V',
             get_parser=float,
-            vals=vals.Numbers(0.250, 0.500))
+            vals=vals.Numbers(0.250, _chan_amps[self.model]))
 
         self.add_parameter('assigned_asset',
                            label=('Waveform/sequence assigned to '
@@ -232,21 +248,19 @@ def __init__(self,  parent: Instrument, name: str, channel: int) -> None:
             self.add_parameter(
                 'marker{}_high'.format(mrk),
                 label='Channel {} marker {} high level'.format(channel, mrk),
-                set_cmd='SOURce{}:MARKer{}:VOLTage:HIGH {{}}'.format(channel,
-                                                                     mrk),
+                set_cmd=partial(self._set_marker, channel, mrk, True),
                 get_cmd='SOURce{}:MARKer{}:VOLTage:HIGH?'.format(channel, mrk),
                 unit='V',
-                vals=vals.Numbers(-1.4, 1.4),
+                vals=vals.Numbers(*_marker_high[self.model]),
                 get_parser=float)
 
             self.add_parameter(
                 'marker{}_low'.format(mrk),
                 label='Channel {} marker {} low level'.format(channel, mrk),
-                set_cmd='SOURce{}:MARKer{}:VOLTage:LOW {{}}'.format(channel,
-                                                                    mrk),
+                set_cmd=partial(self._set_marker, channel, mrk, False),
                 get_cmd='SOURce{}:MARKer{}:VOLTage:LOW?'.format(channel, mrk),
                 unit='V',
-                vals=vals.Numbers(-1.4, 1.4),
+                vals=vals.Numbers(*_marker_low[self.model]),
                 get_parser=float)
 
             self.add_parameter(
@@ -256,6 +270,13 @@ def __init__(self,  parent: Instrument, name: str, channel: int) -> None:
                 get_cmd='OUTPut{}:WVALue:MARKer{}?'.format(channel, mrk),
                 vals=vals.Enum('FIRST', 'LOW', 'HIGH'))
 
+            self.add_parameter(
+                name=f'marker{mrk}_stoppedvalue',
+                label=f'Channel {channel} marker {mrk} stopped value',
+                set_cmd=f'OUTPut{channel}:SVALue:MARKer{mrk} {{}}',
+                get_cmd=f'OUTPut{channel}:SVALue:MARKer{mrk}?',
+                vals=vals.Enum('OFF', 'LOW'))
+
         ##################################################
         # MISC.
 
@@ -271,6 +292,21 @@ def __init__(self,  parent: Instrument, name: str, channel: int) -> None:
                                       allows for one, and 10 bit
                                       does NOT allow for markers"""))
 
+    def _set_marker(self, channel: int, marker: int,
+                    high: bool, voltage: float) -> None:
+        """
+        Set the marker high/low value and update the low/high value
+        """
+        if high:
+            this = 'HIGH'
+            other = 'low'
+        else:
+            this = 'LOW'
+            other = 'high'
+
+        self.write(f'SOURce{channel}:MARKer{marker}:VOLTage:{this} {voltage}')
+        self.parameters[f'marker{marker}_{other}'].get()
+
     def _set_fgfreq(self, channel: int, frequency: float) -> None:
         """
         Set the function generator frequency
@@ -388,6 +424,13 @@ def __init__(self, name: str, address: str, num_channels: int,
                            unit='Hz',
                            vals=vals.Numbers(6.25e9, 12.5e9))
 
+        self.add_parameter('run_state',
+                           label='Run state',
+                           get_cmd='AWGControl:RSTATe?',
+                           val_mapping={'Stopped': '0',
+                                        'Waiting for trigger': '1',
+                                        'Running': '2'})
+
         # We deem 2 channels too few for a channel list
         if self.num_channels > 2:
             chanlist = ChannelList(self, 'Channels', AWGChannel,
@@ -424,12 +467,36 @@ def force_triggerB(self):
         """
         self.write('TRIGger:IMMediate BTRigger')
 
-    def play(self) -> None:
+    def wait_for_operation_to_complete(self):
+        """
+        Waits for the latest issued overlapping command to finish
+        """
+        self.ask('*OPC?')
+
+    def play(self, wait_for_running: bool=True, timeout: float=10) -> None:
         """
         Run the AWG/Func. Gen. This command is equivalent to pressing the
         play button on the front panel.
+
+        Args:
+            wait_for_running: If True, this command is blocking while the
+                instrument is getting ready to play
+            timeout: The maximal time to wait for the instrument to play.
+                Raises an exception is this time is reached.
         """
         self.write('AWGControl:RUN')
+        if wait_for_running:
+            start_time = time.perf_counter()
+            running = False
+            while not running:
+                time.sleep(0.1)
+                running = self.run_state() == 'Running'
+                waited_for = start_time - time.perf_counter()
+                if waited_for > timeout:
+                    raise RuntimeError(f'Reached timeout ({timeout} s) '
+                                       'while waiting for instrument to play.'
+                                       ' Perhaps some waveform or sequence is'
+                                       ' corrupt?')
 
     def stop(self) -> None:
         """
@@ -466,6 +533,16 @@ def waveformList(self) -> List[str]:
 
         return resp
 
+    def delete_sequence_from_list(self, seqname: str) -> None:
+        """
+        Delete the specified sequence from the sequence list
+
+        Args:
+            seqname: The name of the sequence (as it appears in the sequence
+                list, not the file name) to delete
+        """
+        self.write(f'SLISt:SEQuence:DELete "{seqname}"')
+
     def clearSequenceList(self):
         """
         Clear the sequence list
@@ -553,7 +630,7 @@ def sendWFMXFile(self, wfmx: bytes, filename: str,
         self._sendBinaryFile(wfmx, filename, path)
 
     def _sendBinaryFile(self, binfile: bytes, filename: str,
-                        path: str) -> None:
+                        path: str, overwrite: bool=True) -> None:
         """
         Send a binary file to the AWG's mass memory (disk).
 
@@ -562,6 +639,7 @@ def _sendBinaryFile(self, binfile: bytes, filename: str,
             filename: The name of the file on the AWG disk, including the
                 extension.
             path: The path to the directory where the file should be saved.
+            overwite: If true, the file on disk gets overwritten
         """
 
         name_str = 'MMEMory:DATA "{}"'.format(filename).encode('ascii')
@@ -578,6 +656,14 @@ def _sendBinaryFile(self, binfile: bytes, filename: str,
 
         self.current_directory(path)
 
+        if overwrite:
+            log.debug(f'Pre-deleting file {filename} at {path}')
+            self.visa_handle.write(f'MMEMory:DELete "{filename}"')
+            # if the file does not exist,
+            # an error code -256 is put in the error queue
+            resp = self.visa_handle.query(f'SYSTem:ERRor:CODE?')
+            log.debug(f'Pre-deletion finished with return code {resp}')
+
         self.visa_handle.write_raw(msg)
 
     def loadWFMXFile(self, filename: str, path: str=None) -> None:
@@ -1020,8 +1106,8 @@ def makeSEQXFile(trig_waits: Sequence[int],
         # (no_of_chans, no_of_elms, no_of_arrays, no_of_points)
         # where no_of_arrays is 3 if both markers are included
         (chans, elms) = np.shape(wfms)[0: 2]
-        wfm_names = [['wfmch{}pos{}'.format(ch, el) for el in range(1, elms+1)]
-                     for ch in range(1, chans+1)]
+        wfm_names = [[f'wfmch{ch}pos{el}' for ch in range(1, chans+1)]
+                     for el in range(1, elms+1)]
 
         # generate wfmx files for the waveforms
         flat_wfmxs = [] # type: List[bytes]


Test patch:
diff --git a/qcodes/tests/drivers/test_tektronix_AWG70000A.py b/qcodes/tests/drivers/test_tektronix_AWG70000A.py
--- a/qcodes/tests/drivers/test_tektronix_AWG70000A.py
+++ b/qcodes/tests/drivers/test_tektronix_AWG70000A.py
@@ -44,6 +44,18 @@ def awg2():
     awg2_sim.close()
 
 
+@pytest.fixture(scope='module')
+def random_wfm_m1_m2_package():
+    """
+    Make a random 2400 points np.array([wfm, m1, m2]).
+    The waveform has values in [-0.1, 0.1)
+    """
+    wfm = 0.2*(np.random.rand(2400) - 0.5)
+    m1 = np.random.randint(0, 2, 2400)
+    m2 = np.random.randint(0, 2, 2400)
+    return np.array([wfm, m1, m2])
+
+
 @pytest.fixture(scope='module')
 def forged_sequence():
     """
@@ -196,3 +208,27 @@ def test_seqxfile_from_fs(forged_sequence):
             etree.XML(str_seq_sml, parser=parser)
 
 # TODO: Add some failing tests for inproper input
+
+
+def test_makeSEQXFile(awg2, random_wfm_m1_m2_package):
+    """
+    Test that this function works (for some input)
+    """
+
+    seqlen = 25
+    chans = 3
+
+    wfmpkg = random_wfm_m1_m2_package
+
+    trig_waits = [0]*seqlen
+    nreps = [1]*seqlen
+    event_jumps = [0]*seqlen
+    event_jump_to = [0]*seqlen
+    go_to = [0]*seqlen
+    wfms = [[wfmpkg for i in range(seqlen)] for j in range(chans)]
+    amplitudes = [0.5]*chans
+    seqname = "testseq"
+
+    seqxfile = awg2.makeSEQXFile(trig_waits, nreps, event_jumps,
+                                 event_jump_to, go_to, wfms,
+                                 amplitudes, seqname)
\ No newline at end of file