Completed
Pull Request — master (#384)
by
unknown
01:25
created

DTG5334._block_new()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
dl 0
loc 8
rs 10
c 1
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""
4
This file contains the Qudi hardware module for the Tektronix DTG 5334.
5
6
Qudi is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
Qudi is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
GNU General Public License for more details.
15
16
You should have received a copy of the GNU General Public License
17
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
18
19
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
20
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
21
"""
22
23
import ctypes
24
from collections import OrderedDict
25
import numpy as np
26
import os
27
import time
28
import visa
29
30
from interface.pulser_interface import PulserInterface, PulserConstraints
31
from core.module import Base, ConfigOption
32
33
34
class DTG5334(Base, PulserInterface):
35
    """
36
        Tektronix DTG 5334
37
    """
38
    _modclass = 'dtg5334'
39
    _modtype = 'hardware'
40
41
    visa_address = ConfigOption('visa_address', missing='error')
42
43
    ch_map = {
44
        'd_ch1': ('A', 1),
45
        'd_ch2': ('A', 2),
46
        'd_ch3': ('B', 1),
47
        'd_ch4': ('B', 2),
48
        'd_ch5': ('C', 1),
49
        'd_ch6': ('C', 2),
50
        'd_ch7': ('D', 1),
51
        'd_ch8': ('D', 2)
52
    }
53
54
    modules_map = {
55
        -1: 'No module',
56
        1: 'DTGM10',
57
        2: 'DTGM20',
58
        3: 'DTGM30',
59
        4: 'DTGM31',
60
        5: 'DTGM31',
61
        6: 'DTGM32'
62
    }
63
64
    stb_values = {
65
        0: 'Wat'
66
    }
67
68
    def on_activate(self):
69
        """ Initialisation performed during activation of the module.
70
        """
71
        self.current_loaded_assets = {}
72
73
        # connect to DTG
74
        self._rm = visa.ResourceManager('@py')
75
76
        self.dtg = self._rm.open_resource(self.visa_address, read_termination='\n\x00')
77
78
        # set timeout by default to 15 sec
79
        self.dtg.timeout = 15000
80
81
        self.connected = True
82
83
        self._mfg, self._model, self._serial, self._fw , self._version = self._get_id()
84
        self.log.debug('Found the following model: {0} {1} {2} {3} {4}'.format(
85
            self._mfg, self._model, self._serial, self._fw, self._version))
86
        self._modules = self._get_modules()
87
        self.log.debug('Found the following modules: {0}'.format(self._modules))
88
89
        self.current_loaded_assets = {}
90
        self.current_loaded_asset_type = ''
91
        self.waveform_names = set()
92
        self.sequence_names = set()
93
94
    def on_deactivate(self):
95
        """ Required tasks to be performed during deactivation of the module.
96
        """
97
        # Closes the connection to the DTG
98
        try:
99
            self.dtg.close()
100
        except:
101
            self.log.debug('Closing DTG connection using pyvisa failed.')
102
        self.log.info('Closed connection to DTG')
103
        self.connected = False
104
        return
105
106
    def get_constraints(self):
107
        """
108
        Retrieve the hardware constrains from the Pulsing device.
109
110
        @return constraints object: object with pulser constraints as attributes.
111
112
        Provides all the constraints (e.g. sample_rate, amplitude, total_length_bins,
113
        channel_config, ...) related to the pulse generator hardware to the caller.
114
115
            SEE PulserConstraints CLASS IN pulser_interface.py FOR AVAILABLE CONSTRAINTS!!!
116
117
        If you are not sure about the meaning, look in other hardware files to get an impression.
118
        If still additional constraints are needed, then they have to be added to the
119
        PulserConstraints class.
120
121
        Each scalar parameter is an ScalarConstraints object defined in cor.util.interfaces.
122
        Essentially it contains min/max values as well as min step size, default value and unit of
123
        the parameter.
124
125
        PulserConstraints.activation_config differs, since it contain the channel
126
        configuration/activation information of the form:
127
            {<descriptor_str>: <channel_list>,
128
             <descriptor_str>: <channel_list>,
129
             ...}
130
131
        If the constraints cannot be set in the pulsing hardware (e.g. because it might have no
132
        sequence mode) just leave it out so that the default is used (only zeros).
133
        """
134
        # Example for configuration with default values:
135
        constraints = PulserConstraints()
136
137
        constraints.sample_rate.min = 50e3
138
        constraints.sample_rate.max = 3.35e9
139
        constraints.sample_rate.step = 1e3
140
        constraints.sample_rate.default = 12.0e9
141
142
        constraints.a_ch_amplitude.min = 0.0
143
        constraints.a_ch_amplitude.max = 0.0
144
        constraints.a_ch_amplitude.step = 0.0
145
        constraints.a_ch_amplitude.default = 0.0
146
147
        constraints.a_ch_offset.min = 0.0
148
        constraints.a_ch_offset.max = 0.0
149
        constraints.a_ch_offset.step = 0.0
150
        constraints.a_ch_offset.default = 0.0
151
152
        constraints.d_ch_low.min = -2.0
153
        constraints.d_ch_low.max = 2.44
154
        constraints.d_ch_low.step = 0.05
155
        constraints.d_ch_low.default = 0.0
156
157
        constraints.d_ch_high.min = -1.0
158
        constraints.d_ch_high.max = 2.47
159
        constraints.d_ch_high.step = 0.05
160
        constraints.d_ch_high.default = 2.4
161
162
        constraints.waveform_length.min = 80
163
        constraints.waveform_length.max = 64800000
164
        constraints.waveform_length.step = 1
165
        constraints.waveform_length.default = 80
166
167
        constraints.waveform_num.min = 1
168
        constraints.waveform_num.max = 32000
169
        constraints.waveform_num.step = 1
170
        constraints.waveform_num.default = 1
171
172
        constraints.sequence_num.min = 1
173
        constraints.sequence_num.max = 8000
174
        constraints.sequence_num.step = 1
175
        constraints.sequence_num.default = 1
176
177
        constraints.subsequence_num.min = 1
178
        constraints.subsequence_num.max = 4000
179
        constraints.subsequence_num.step = 1
180
        constraints.subsequence_num.default = 1
181
182
        # If sequencer mode is available then these should be specified
183
        constraints.repetitions.min = 0
184
        constraints.repetitions.max = 65539
185
        constraints.repetitions.step = 1
186
        constraints.repetitions.default = 0
187
188
        constraints.event_triggers = ['A', 'B']
189
        constraints.flags = list()
190
191
        constraints.sequence_steps.min = 0
192
        constraints.sequence_steps.max = 8000
193
        constraints.sequence_steps.step = 1
194
        constraints.sequence_steps.default = 0
195
196
        # the name a_ch<num> and d_ch<num> are generic names, which describe UNAMBIGUOUSLY the
197
        # channels. Here all possible channel configurations are stated, where only the generic
198
        # names should be used. The names for the different configurations can be customary chosen.
199
        activation_conf = OrderedDict()
200
        activation_conf['A'] = {'d_ch1', 'd_ch2'}
201
        activation_conf['B'] = {'d_ch3', 'd_ch4'}
202
        activation_conf['C'] = {'d_ch5', 'd_ch6'}
203
        activation_conf['D'] = {'d_ch7', 'd_ch8'}
204
        activation_conf['AB'] = {'d_ch1', 'd_ch2', 'd_ch3', 'd_ch4'}
205
        activation_conf['ABC'] = {'d_ch1', 'd_ch2', 'd_ch3', 'd_ch4', 'd_ch5', 'd_ch6'}
206
        activation_conf['all'] = {
207
            'd_ch1', 'd_ch2', 'd_ch3', 'd_ch4', 'd_ch5', 'd_ch6', 'd_ch7', 'd_ch8'
208
        }
209
        constraints.activation_config = activation_conf
210
        return constraints
211
212
    def pulser_on(self):
213
        """ Switches the pulsing device on.
214
215
        @return int: error code (0:OK, -1:error)
216
        """
217
        self.dtg.write('OUTP:STAT:ALL ON;*WAI')
218
        self.dtg.write('TBAS:RUN ON')
219
        state = 0 if int(self.dtg.query('TBAS:RUN?')) == 1 else -1
220
        return state
221
222
    def pulser_off(self):
223
        """ Switches the pulsing device off.
224
225
        @return int: error code (0:OK, -1:error)
226
        """
227
        self.dtg.write('OUTP:STAT:ALL OFF;*WAI')
228
        self.dtg.write('TBAS:RUN OFF')
229
        state = 0 if int(self.dtg.query('TBAS:RUN?')) == 0 else -1
230
        return state
231
232
    def load_waveform(self, load_dict):
233
        """ Loads a waveform to the specified channel of the pulsing device.
234
        For devices that have a workspace (i.e. AWG) this will load the waveform from the device
235
        workspace into the channel.
236
        For a device without mass memory this will make the waveform/pattern that has been
237
        previously written with self.write_waveform ready to play.
238
239
        @param load_dict:  dict|list, a dictionary with keys being one of the available channel
240
                                      index and values being the name of the already written
241
                                      waveform to load into the channel.
242
                                      Examples:   {1: rabi_ch1, 2: rabi_ch2} or
243
                                                  {1: rabi_ch2, 2: rabi_ch1}
244
                                      If just a list of waveform names if given, the channel
245
                                      association will be invoked from the channel
246
                                      suffix '_ch1', '_ch2' etc.
247
248
        @return dict: Dictionary containing the actually loaded waveforms per channel.
249
        """
250
        pass
251
252
    def load_sequence(self, sequence_name):
253
        """ Loads a sequence to the channels of the device in order to be ready for playback.
254
        For devices that have a workspace (i.e. AWG) this will load the sequence from the device
255
        workspace into the channels.
256
        For a device without mass memory this will make the waveform/pattern that has been
257
        previously written with self.write_waveform ready to play.
258
259
        @param sequence_name:  dict|list, a dictionary with keys being one of the available channel
260
                                      index and values being the name of the already written
261
                                      waveform to load into the channel.
262
                                      Examples:   {1: rabi_ch1, 2: rabi_ch2} or
263
                                                  {1: rabi_ch2, 2: rabi_ch1}
264
                                      If just a list of waveform names if given, the channel
265
                                      association will be invoked from the channel
266
                                      suffix '_ch1', '_ch2' etc.
267
268
        @return dict: Dictionary containing the actually loaded waveforms per channel.
269
        """
270
        pass
271
272
    def get_loaded_assets(self):
273
        """
274
        Retrieve the currently loaded asset names for each active channel of the device.
275
276
        @return (dict, str): Dictionary with keys being the channel number and values being the
277
                             respective asset loaded into the channel,
278
                             string describing the asset type ('waveform' or 'sequence')
279
        """
280
        return self.current_loaded_assets, self.current_loaded_asset_type
281
282
    def clear_all(self):
283
        """ Clears all loaded waveforms from the pulse generators RAM/workspace.
284
285
        @return int: error code (0:OK, -1:error)
286
        """
287
        self.dtg.write('GROUP:DEL:ALL;*WAI')
288
        self.dtg.write('BLOC:DEL:ALL;*WAI')
289
        self.current_loaded_assets = {}
290
        return 0
291
292
    def get_status(self):
293
        """ Retrieves the status of the pulsing hardware
294
295
        @return (int, dict): tuple with an integer value of the current status and a corresponding
296
                             dictionary containing status description for all the possible status
297
                             variables of the pulse generator hardware.
298
        """
299
        status = 0
300
        return status, self.stb_values
301
302
    def get_sample_rate(self):
303
        """ Get the sample rate of the pulse generator hardware
304
305
        @return float: The current sample rate of the device (in Hz)
306
307
        Do not return a saved sample rate from an attribute, but instead retrieve the current
308
        sample rate directly from the device.
309
        """
310
        return float(self.dtg.query('TBAS:FREQ?'))
311
312
    def set_sample_rate(self, sample_rate):
313
        """ Set the sample rate of the pulse generator hardware.
314
315
        @param float sample_rate: The sampling rate to be set (in Hz)
316
317
        @return float: the sample rate returned from the device (in Hz).
318
319
        Note: After setting the sampling rate of the device, use the actually set return value for
320
              further processing.
321
        """
322
        self.dtg.write('TBAS:FREQ {0:e}'.format(sample_rate))
323
        return self.get_sample_rate()
324
325
    def get_analog_level(self, amplitude=None, offset=None):
326
        """ Device has no analog channels.
327
        """
328
        return {}, {}
329
330
    def set_analog_level(self, amplitude=None, offset=None):
331
        """ Device has no analog channels.
332
        """
333
        return {}, {}
334
335
    def get_digital_level(self, low=None, high=None):
336
        """ Retrieve the digital low and high level of the provided/all channels.
337
338
        @param list low: optional, if the low value (in Volt) of a specific channel is desired.
339
        @param list high: optional, if the high value (in Volt) of a specific channel is desired.
340
341
        @return: (dict, dict): tuple of two dicts, with keys being the channel descriptor strings
342
                               (i.e. 'd_ch1', 'd_ch2') and items being the values for those
343
                               channels. Both low and high value of a channel is denoted in volts.
344
345
        Note: Do not return a saved low and/or high value but instead retrieve
346
              the current low and/or high value directly from the device.
347
        """
348
        if low is None:
349
            low = self.get_constraints().activation_config['all']
350
        if high is None:
351
            high = self.get_constraints().activation_config['all']
352
353
        ch_low = {
354
            chan:
355
                float(
356
                    self.dtg.query('PGEN{0}:CH{1}:LOW?'.format(
357
                        *(self.ch_map[chan])
358
                    ))
359
                )
360
            for chan in low
361
        }
362
363
        ch_high = {
364
            chan:
365
                float(
366
                    self.dtg.query('PGEN{0}:CH{1}:HIGH?'.format(
367
                        *(self.ch_map[chan])
368
                    ))
369
                )
370
            for chan in high
371
        }
372
373
        return ch_high, ch_low
374
375
    def set_digital_level(self, low=None, high=None):
376
        """ Set low and/or high value of the provided digital channel.
377
378
        @param dict low: dictionary, with key being the channel descriptor string
379
                         (i.e. 'd_ch1', 'd_ch2') and items being the low values (in volt) for the
380
                         desired channel.
381
        @param dict high: dictionary, with key being the channel descriptor string
382
                          (i.e. 'd_ch1', 'd_ch2') and items being the high values (in volt) for the
383
                          desired channel.
384
385
        @return (dict, dict): tuple of two dicts where first dict denotes the current low value and
386
                              the second dict the high value for ALL digital channels.
387
                              Keys are the channel descriptor strings (i.e. 'd_ch1', 'd_ch2')
388
389
        If nothing is passed then the command will return the current voltage levels.
390
391
        Note: After setting the high and/or low values of the device, use the actual set return
392
              values for further processing.
393
        """
394
        if low is None:
395
            low = {}
396
        if high is None:
397
            high = {}
398
399
        for chan, level in low.items():
400
            gen, gen_ch = self.ch_map[chan]
401
            self.dtg.write('PGEN{0}:CH{1}:LOW {2}'.format(gen, gen_ch, level))
402
403
        for chan, level in high.items():
404
            gen, gen_ch = self.ch_map[chan]
405
            self.dtg.write('PGEN{0}:CH{1}:HIGH {2}'.format(gen, gen_ch, level))
406
407
        return self.get_digital_level()
408
409
    def get_active_channels(self, ch=None):
410
        """ Get the active channels of the pulse generator hardware.
411
412
        @param list ch: optional, if specific analog or digital channels are needed to be asked
413
                        without obtaining all the channels.
414
415
        @return dict:  where keys denoting the channel string and items boolean expressions whether
416
                       channel are active or not.
417
418
        If no parameter (or None) is passed to this method all channel states will be returned.
419
        """
420
        if ch is None:
421
            chan_list = self.get_constraints().activation_config['all']
422
        active_ch = {chan: 1 for chan in chan_list}
423
424
        return active_ch
425
426
    def set_active_channels(self, ch=None):
427
        """ Set the active channels for the pulse generator hardware.
428
429
        @param dict ch: dictionary with keys being the analog or digital string generic names for
430
                        the channels (i.e. 'd_ch1', 'a_ch2') with items being a boolean value.
431
                        True: Activate channel, False: Deactivate channel
432
433
        @return dict: with the actual set values for ALL active analog and digital channels
434
435
        If nothing is passed then the command will simply return the unchanged current state.
436
        """
437
        for chan, state in ch.items():
438
            gen, gen_ch = self.ch_map[chan]
439
            b_state = 1 if state else 0
440
            self.dtg.write('PGEN{0}:CH{1}:OUTP {2}'.format(gen, gen_ch, b_state))
441
442
        return self.get_active_channels()
443
444
    def write_waveform(self, name, analog_samples, digital_samples, is_first_chunk, is_last_chunk,
445
                       total_number_of_samples):
446
        """
447
        Write a new waveform or append samples to an already existing waveform on the device memory.
448
        The flags is_first_chunk and is_last_chunk can be used as indicator if a new waveform should
449
        be created or if the write process to a waveform should be terminated.
450
451
        @param name: str, the name of the waveform to be created/append to
452
        @param analog_samples: numpy.ndarray of type float32 containing the voltage samples
453
        @param digital_samples: numpy.ndarray of type bool containing the marker states
454
                                (if analog channels are active, this must be the same length as
455
                                analog_samples)
456
        @param is_first_chunk: bool, flag indicating if it is the first chunk to write.
457
                                     If True this method will create a new empty wavveform.
458
                                     If False the samples are appended to the existing waveform.
459
        @param is_last_chunk: bool, flag indicating if it is the last chunk to write.
460
                                    Some devices may need to know when to close the appending wfm.
461
        @param total_number_of_samples: int, The number of sample points for the entire waveform
462
                                        (not only the currently written chunk)
463
464
        @return: (int, list) number of samples written (-1 indicates failed process) and list of
465
                             created waveform names
466
        """
467
        # check input
468
        if not name:
469
            self.log.error('Please specify a name for waveform creation.')
470
            return -1
471
472
        min_samples = 960
473
        longest_channel = max([len(v) for k, v in digital_samples.items()])
474
        print('Loading block with', longest_channel, 'samples')
475
        if longest_channel < min_samples:
476
            self.log.error('Minimum waveform length for DTG5334 series is {0} samples.\n'
477
                           'Direct waveform creation for {1} failed.'.format(min_samples, name))
478
            return -1
479
480
        # determine active channels
481
        activation_dict = self.get_active_channels()
482
        active_chnl = [chnl for chnl in activation_dict if activation_dict[chnl]]
483
        active_digital = [chnl for chnl in active_chnl if 'd_ch' in chnl]
484
        active_digital.sort()
485
        print(active_digital)
486
487
        # Sanity check of channel numbers
488
        if set(active_digital) != set(digital_samples.keys()):
489
            self.log.error(
490
                'Mismatch of channel activation and sample array dimensions for direct '
491
                'write.\nChannel activation is: {}.\n'
492
                'Sample arrays have: {}.'
493
                ''.format(active_digital, list(digital_samples.keys())))
494
            return -1
495
496
        self._block_new(name, longest_channel)
497
        self.log.debug(self.dtg.query('BLOC:SEL?'))
498
        written = self._block_write(name, digital_samples)
499
        print(written)
500
        self.current_loaded_assets = {int(ch.split('_ch')[1]): name for ch in active_digital}
501
        self.current_loaded_asset_type = 'waveform'
502
        self.waveform_names.add(name)
503
        return max(written), [name]
504
505
    def write_sequence(self, name, sequence_parameters):
506
        """
507
        Write a new sequence on the device memory.
508
509
        @param name: str, the name of the waveform to be created/append to
510
        @param sequence_parameters: dict, dictionary containing the parameters for a sequence
511
512
        @return: int, number of sequence steps written (-1 indicates failed process)
513
        """
514
        num_steps = len(sequence_parameters)
515
516
        # Check if sequence already exists and delete if necessary.
517
        #if sequence_name in self._get_sequence_names_memory():
518
        #    self.dtg.write('BLOC:DEL "{0}"'.format(sequence_name))
519
        self._set_sequence_length(num_steps)
520
        for line_nr, (wfms, params) in enumerate(sequence_parameters):
521
            print(line_nr, params)
522
            go_to = '' if params['go_to'] <= 0 else params['go_to']
523
            jump_to = '' if params['event_jump_to'] <= 0 else params['event_jump_to']
524
            reps = 0 if params['repetitions'] <= 0 else params['repetitions']
525
            self._set_sequence_line(
526
                line_nr,
527
                '{0}'.format(line_nr + 1),
528
                0,
529
                params['name'][0].rsplit('.')[0],
530
                reps,
531
                jump_to,
532
                go_to
533
            )
534
535
        # Wait for everything to complete
536
        while int(self.dtg.query('*OPC?')) != 1:
537
            time.sleep(0.2)
538
539
        self.sequence_names.add(name)
540
        return 0
541
542
    def get_waveform_names(self):
543
        """ Retrieve the names of all uploaded waveforms on the device.
544
545
        @return list: List of all uploaded waveform name strings in the device workspace.
546
        """
547
        return list(sorted(self.waveform_names))
548
549
    def get_sequence_names(self):
550
        """ Retrieve the names of all uploaded sequence on the device.
551
552
        @return list: List of all uploaded sequence name strings in the device workspace.
553
        """
554
        return list(sorted(self.sequence_names))
555
556
    def delete_waveform(self, waveform_name):
557
        """ Delete the waveform with name "waveform_name" from the device memory.
558
559
        @param str waveform_name: The name of the waveform to be deleted
560
                                  Optionally a list of waveform names can be passed.
561
562
        @return list: a list of deleted waveform names.
563
        """
564
        return []
565
566
    def delete_sequence(self, sequence_name):
567
        """ Delete the sequence with name "sequence_name" from the device memory.
568
569
        @param str sequence_name: The name of the sequence to be deleted
570
                                  Optionally a list of sequence names can be passed.
571
572
        @return list: a list of deleted sequence names.
573
        """
574
        return []
575
576
    def get_interleave(self):
577
        """ Check whether Interleave is ON or OFF in AWG.
578
579
        @return bool: True: ON, False: OFF
580
581
        Will always return False for pulse generator hardware without interleave.
582
        """
583
        return False
584
585
    def set_interleave(self, state=False):
586
        """ Turns the interleave of an AWG on or off.
587
588
        @param bool state: The state the interleave should be set to
589
                           (True: ON, False: OFF)
590
591
        @return bool: actual interleave status (True: ON, False: OFF)
592
593
        Unused for pulse generator hardware other than an AWG.
594
        """
595
        return False
596
597
    def write(self, command):
598
        """ Sends a command string to the device.
599
600
        @param string command: string containing the command
601
602
        @return int: error code (0:OK, -1:error)
603
        """
604
        self.dtg.write(command)
605
606
    def query(self, question):
607
        """ Asks the device a 'question' and receive and return an answer from it.
608
609
        @param string question: string containing the command
610
611
        @return string: the answer of the device to the 'question' in a string
612
        """
613
        return self.dtg.query(question)
614
615
    def reset(self):
616
        """ Reset the device.
617
618
        @return int: error code (0:OK, -1:error)
619
        """
620
        self.dtg.write('*RST')
621
622
    def has_sequence_mode(self):
623
        """ Asks the pulse generator whether sequence mode exists.
624
625
        @return: bool, True for yes, False for no.
626
        """
627
        return True
628
629
    def _get_id(self):
630
        result = self.dtg.query('*IDN?')
631
        version = self.dtg.query('SYSTEM:VERSION?')
632
        ret = result.replace('\n', '').split(',')
633
        ret.append(version.replace('\n', ''))
634
        return ret
635
636
    def _get_modules(self):
637
        a = self.modules_map[int(self.dtg.query('PGENA:ID?'))]
638
        b = self.modules_map[int(self.dtg.query('PGENB:ID?'))]
639
        c = self.modules_map[int(self.dtg.query('PGENC:ID?'))]
640
        d = self.modules_map[int(self.dtg.query('PGEND:ID?'))]
641
        return [a, b, c, d]
642
643
    def _is_output_on(self):
644
        return int(self.dtg.query('TBAS:RUN?')) == 1
645
646
    def _block_length(self, name):
647
        return int(self.dtg.query('BLOC:LENG? "{0}"'.format(name)))
648
649
    def _block_exists(self, name):
650
        return self._block_length(name) != -1
651
652
    def _block_delete(self, name):
653
        self.dtg.write('BLOC:DEL "{0}"'.format(name))
654
655
    def _block_new(self, name, length):
656
        if self._block_exists(name):
657
            self._block_delete(name)
658
659
        self.dtg.write('BLOC:NEW "{0}", {1}'.format(name, length))
660
        self.dtg.query('*OPC?')
661
        self.dtg.write('BLOC:SEL "{0}"'.format(name))
662
        self.dtg.query('*OPC?')
663
664
    def _block_write(self, name, digital_samples):
665
        written = []
666
        self.dtg.write('BLOC:SEL "{0}"'.format(name))
667
668
        for ch, data in sorted(digital_samples.items()):
669
            written.append(self._channel_write_binary(ch, data))
670
671
        self.dtg.query('*OPC?')
672
        return written
673
674
    def _channel_write(self, channel, data):
675
        c = self.ch_map[channel]
676
        max_blocksize = 500
677
        dlen = len(data)
678
        written = 0
679
        start = 0
680
681
        # when there is more than 1MB of data to transfer, split it up
682
        print('Starting chunked transfer')
683
        while dlen >= max_blocksize:
684
            end = start + max_blocksize
685
            datstr = ''.join(map(lambda x: str(int(x)), data[start:end]))
686
            print(channel, 'loop', dlen, len(datstr))
687
            self.dtg.write('PGEN{0}:CH{1}:DATA {2},{3},"{4}"'.format(
688
                c[0], c[1], start, end - start, datstr))
689
            self.dtg.query('*OPC?')
690
            written += end - start
691
            dlen -= end - start
692
            start = end
693
694
        end = start + dlen
695
        if dlen > 0:
696
            datstr = ''.join(map(lambda x: str(int(x)), data[start:end]))
697
            print(channel, 'last', len(datstr))
698
            self.dtg.write(
699
                'PGEN{0}:CH{1}:DATA {2},{3},"{4}"'.format(
700
                    c[0], c[1], start, end - start, datstr)
701
            )
702
            self.dtg.query('*OPC?')
703
            written += end - start
704
        return written
705
706
    def _channel_write_binary(self, channel, data):
707
        c = self.ch_map[channel]
708
        max_blocksize = 8 * 800
709
        dlen = len(data)
710
        written = 0
711
        start = 0
712
713
        # when there is more than 1MB of data to transfer, split it up
714
        while dlen >= max_blocksize - 8:
715
            end = start + max_blocksize
716
            bytestr = np.packbits(np.fliplr(np.reshape(data[start:end], (-1, 8))))
717
            print(channel, '->', c, 'start', start, 'end', end, 'len', dlen, 'packed', len(bytestr))
718
            #print(bytestr)
719
            self.dtg.write_binary_values(
720
                'PGEN{0}:CH{1}:BDATA {2},{3},'.format(c[0], c[1], start, end - start),
721
                bytestr,
722
                datatype='B'
723
            )
724
            print(self.dtg.query('*OPC?'))
725
            written += end - start
726
            dlen -= end - start
727
            start = end
728
729
        end = start + dlen
730
        if dlen > 0:
731
            to_pad = 8 - dlen % 8 if dlen % 8 != 0 else 0
732
733
            padded_bytes = np.packbits(
734
                np.fliplr(
735
                    np.reshape(
736
                        np.pad(data[start:end], (0, to_pad), 'constant'),
737
                        (-1, 8)
738
                    )
739
                )
740
            )
741
            #print(padded_bytes)
742
            print(channel, '-->', c, 'start', start, 'end', end,
743
                  'len', dlen, 'padded', len(padded_bytes))
744
            self.dtg.write_binary_values(
745
                'PGEN{0}:CH{1}:BDATA {2},{3},'.format(c[0], c[1], start, end - start),
746
                padded_bytes,
747
                datatype='B'
748
            )
749
            print(self.dtg.query('*OPC?'))
750
            written += end - start
751
        return written
752
753
    def _get_sequence_line(self, line_nr):
754
        fields = self.dtg.query('SEQ:DATA? {0}'.format(line_nr)).split(', ')
755
        print(fields)
756
        label, trigger, block, repeat, jump, goto = fields
757
        return (
758
            label.strip('"'),
759
            int(trigger),
760
            block.strip('"'),
761
            int(repeat),
762
            jump.strip('"'),
763
            goto.strip('"')
764
        )
765
766
    def _set_sequence_line(self, line_nr, label, trigger, block, repeat, jump, goto):
767
        print(line_nr, label, trigger, block, repeat, jump, goto)
768
        self.dtg.write('SEQ:DATA {0}, "{1}", {2}, "{3}", {4}, "{5}", "{6}"'.format(
769
            line_nr, label, trigger, block, repeat, jump, goto
770
        ))
771
772
    def _get_sequence_length(self):
773
        return int(self.dtg.query('SEQ:LENG?'))
774
775
    def _set_sequence_length(self, length):
776
        self.dtg.write('SEQ:LENG {0}'.format(length))
777
778
    def _get_sequencer_mode(self):
779
        return self.dtg.query('TBAS:SMODE?')
780