Coverage for C:\Users\hjanssen\HOME\pyCharmProjects\ethz_hvl\hvl_ccb\hvl_ccb\dev\rs_rto1024.py : 37%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2019-2020 ETH Zurich, SIS ID and HVL D-ITET
2#
3"""
4Python module for the Rhode & Schwarz RTO 1024 oscilloscope.
5The communication to the device is through VISA, type TCPIP / INSTR.
6"""
8import logging
9import re
10from pathlib import PureWindowsPath
11from time import sleep
12from typing import List, Tuple, Union
14from .visa import (
15 VisaDevice,
16 VisaDeviceConfig,
17 _VisaDeviceConfigBase,
18 _VisaDeviceConfigDefaultsBase,
19)
20from ..comm.visa import VisaCommunication, VisaCommunicationConfig
21from ..configuration import configdataclass
22from ..utils.enum import AutoNumberNameEnum
23from ..utils.typing import Number
26class RTO1024Error(Exception):
27 pass
30@configdataclass
31class _RTO1024ConfigBase(_VisaDeviceConfigBase):
33 waveforms_path: str
34 """
35 Windows directory on the oscilloscope filesystem for storing waveforms.
36 Mind escaping the backslashes of the path.
37 """
39 settings_path: str
40 """
41 Windows directory on the oscilloscope filesystem for storing settings .dfl files.
42 Mind escaping the backslashes of the path.
43 """
45 backup_path: str
46 """
47 Windows directory on the oscilloscope filesystem for use as backup directory for
48 waveforms. Mind escaping the backslashes of the path.
49 """
52@configdataclass
53class _RTO1024ConfigDefaultsBase(_VisaDeviceConfigDefaultsBase):
55 command_timeout_seconds: Number = 60
56 """
57 Timeout to wait for asynchronous commands to complete, in seconds. This timeout
58 is respected for long operations such as storing waveforms.
59 """
60 wait_sec_short_pause: Number = 0.1
61 """Time for short wait periods, in seconds (depends on both device and
62 network/connection)."""
63 wait_sec_enable_history: Number = 1
64 """Time to wait after enabling history, in seconds."""
65 wait_sec_post_acquisition_start: Number = 2
66 """Time to wait after start of continuous acquisition, in seconds."""
68 def clean_values(self):
69 super().clean_values()
71 if self.command_timeout_seconds <= 0:
72 raise ValueError(
73 "Timeout to wait for asynchronous commands to complete must be a "
74 "positive value (in seconds)."
75 )
76 if self.wait_sec_short_pause <= 0:
77 raise ValueError(
78 "Wait time for a short pause must be a positive value (in seconds)."
79 )
80 if self.wait_sec_enable_history <= 0:
81 raise ValueError(
82 "Wait time for enabling history must be a positive value (in seconds)."
83 )
84 if self.wait_sec_post_acquisition_start <= 0:
85 raise ValueError(
86 "Wait time after acquisition start must be a positive value (in "
87 "seconds)."
88 )
91@configdataclass
92class RTO1024Config(VisaDeviceConfig, _RTO1024ConfigDefaultsBase, _RTO1024ConfigBase):
93 """
94 Configdataclass for the RTO1024 device.
95 """
97 pass
100@configdataclass
101class RTO1024VisaCommunicationConfig(VisaCommunicationConfig):
102 """
103 Configuration dataclass for VisaCommunication with specifications for the RTO1024
104 device class.
105 """
107 interface_type: Union[
108 str, VisaCommunicationConfig.InterfaceType
109 ] = VisaCommunicationConfig.InterfaceType.TCPIP_INSTR # type: ignore
112class RTO1024VisaCommunication(VisaCommunication):
113 """
114 Specialization of VisaCommunication for the RTO1024 oscilloscope
115 """
117 @staticmethod
118 def config_cls():
119 return RTO1024VisaCommunicationConfig
122class RTO1024(VisaDevice):
123 """
124 Device class for the Rhode & Schwarz RTO 1024 oscilloscope.
125 """
127 class TriggerModes(AutoNumberNameEnum):
128 """
129 Enumeration for the three available trigger modes.
130 """
132 AUTO = ()
133 NORMAL = ()
134 FREERUN = ()
136 @classmethod
137 def names(cls):
138 """
139 Returns a list of the available trigger modes.
140 :return: list of strings
141 """
143 return list(map(lambda x: x.name, cls))
145 @staticmethod
146 def config_cls():
147 return RTO1024Config
149 @staticmethod
150 def default_com_cls():
151 return RTO1024VisaCommunication
153 def __init__(
154 self,
155 com: Union[RTO1024VisaCommunication, RTO1024VisaCommunicationConfig, dict],
156 dev_config: Union[RTO1024Config, dict],
157 ):
158 super().__init__(com, dev_config)
160 def start(self) -> None:
161 """
162 Start the RTO1024 oscilloscope and bring it into a defined state and remote
163 mode.
164 """
166 super().start()
168 # go to remote mode
169 self.com.write(">R")
171 # reset device (RST) and clear status registers (CLS)
172 self.com.write("*RST", "*CLS")
174 # enable local display
175 self.local_display(True)
177 # enable status register
178 self.com.write("*ESE 127")
180 def stop(self) -> None:
181 """
182 Stop the RTO1024 oscilloscope, reset events and close communication. Brings
183 back the device to a state where local operation is possible.
184 """
186 if self._spoll_thread is not None and self._spoll_thread.is_polling():
187 # disable any events, EventStatusEnable ESE = 0
188 self.com.write("*ESE 0")
190 # disable any service requests SRE = 0
191 self.com.write("*SRE 0")
193 # device clear: abort processing of any commands
194 self.com.write("&DCL")
196 # go to local mode
197 self.com.write(">L")
198 else:
199 logging.warning("RTO1024 was already stopped")
201 super().stop()
203 def local_display(self, state: bool) -> None:
204 """
205 Enable or disable local display of the scope.
207 :param state: is the desired local display state
208 """
209 state_str = "ON" if state else "OFF"
210 self.com.write(f"SYST:DISP:UPD {state_str}")
212 def set_acquire_length(self, timerange: float) -> None:
213 r"""
214 Defines the time of one acquisition, that is the time across the 10 divisions
215 of the diagram.
217 * Range: 250E-12 ... 500 [s]
218 * Increment: 1E-12 [s]
219 * \*RST = 0.5 [s]
221 :param timerange: is the time for one acquisition. Range: 250e-12 ... 500 [s]
222 """
224 self.com.write(f"TIMebase:RANGe {timerange:G}")
226 def get_acquire_length(self) -> float:
227 r"""
228 Gets the time of one acquisition, that is the time across the 10 divisions
229 of the diagram.
231 * Range: 250E-12 ... 500 [s]
232 * Increment: 1E-12 [s]
234 :return: the time for one acquisition. Range: 250e-12 ... 500 [s]
235 """
237 return float(self.com.query("TIMebase:RANGe?"))
239 def set_reference_point(self, percentage: int) -> None:
240 r"""
241 Sets the reference point of the time scale in % of the display.
242 If the "Trigger offset" is zero, the trigger point matches the reference point.
243 ReferencePoint = zero pint of the time scale
245 * Range: 0 ... 100 [%]
246 * Increment: 1 [%]
247 * \*RST = 50 [%]
249 :param percentage: is the reference in %
250 """
252 self.com.write(f"TIMebase:REFerence {percentage:d}")
254 def get_reference_point(self) -> int:
255 r"""
256 Gets the reference point of the time scale in % of the display.
257 If the "Trigger offset" is zero, the trigger point matches the reference point.
258 ReferencePoint = zero pint of the time scale
260 * Range: 0 ... 100 [%]
261 * Increment: 1 [%]
263 :return: the reference in %
264 """
266 return int(self.com.query("TIMebase:REFerence?"))
268 def set_repetitions(self, number: int) -> None:
269 r"""
270 Set the number of acquired waveforms with RUN Nx SINGLE. Also defines the
271 number of waveforms used to calculate the average waveform.
273 * Range: 1 ... 16777215
274 * Increment: 10
275 * \*RST = 1
277 :param number: is the number of waveforms to acquire
278 """
280 self.com.write(f"ACQuire:COUNt {number:d}")
282 def get_repetitions(self) -> int:
283 r"""
284 Get the number of acquired waveforms with RUN Nx SINGLE. Also defines the
285 number of waveforms used to calculate the average waveform.
287 * Range: 1 ... 16777215
288 * Increment: 10
289 * \*RST = 1
291 :return: the number of waveforms to acquire
292 """
294 return int(self.com.query("ACQuire:COUNt?"))
296 def set_channel_state(self, channel: int, state: bool) -> None:
297 """
298 Switches the channel signal on or off.
300 :param channel: is the input channel (1..4)
301 :param state: is True for on, False for off
302 """
304 self.com.write(f"CHANnel{channel}:STATe {'ON' if state else 'OFF'}")
306 def get_channel_state(self, channel: int) -> bool:
307 """
308 Queries if the channel is active or not.
310 :param channel: is the input channel (1..4)
311 :return: True if active, else False
312 """
314 return self.com.query(f"CHANnel{channel}:STATe?") == "1"
316 def set_channel_scale(self, channel: int, scale: float) -> None:
317 r"""
318 Sets the vertical scale for the indicated channel.
319 The scale value is given in volts per division.
321 * Range for scale: depends on attenuation factor and coupling. With
322 1:1 probe and external attenuations and 50 Ω input
323 coupling, the vertical scale (input sensitivity) is 1
324 mV/div to 1 V/div. For 1 MΩ input coupling, it is 1
325 mV/div to 10 V/div. If the probe and/or external
326 attenuation is changed, multiply the values by the
327 attenuation factors to get the actual scale range.
329 * Increment: 1e-3
330 * \*RST = 0.05
332 See also:
333 set_channel_range
335 :param channel: is the channel number (1..4)
336 :param scale: is the vertical scaling [V/div]
337 """
339 self.com.write(f"CHANnel{channel}:SCALe {scale:4.3f}")
341 def get_channel_scale(self, channel: int) -> float:
342 """
343 Queries the channel scale in V/div.
345 :param channel: is the input channel (1..4)
346 :return: channel scale in V/div
347 """
349 return float(self.com.query(f"CHANnel{channel}:SCALe?"))
351 def set_channel_range(self, channel: int, v_range: float) -> None:
352 r"""
353 Sets the voltage range across the 10 vertical divisions of the diagram. Use
354 the command alternatively instead of set_channel_scale.
356 * Range for range: Depends on attenuation factors and coupling. With
357 1:1 probe and external attenuations and 50 Ω input
358 coupling, the range is 10 mV to 10 V. For 1 MΩ
359 input coupling, it is 10 mV to 100 V. If the probe
360 and/or external attenuation is changed, multiply the
361 range values by the attenuation factors.
363 * Increment: 0.01
364 * \*RST = 0.5
366 :param channel: is the channel number (1..4)
367 :param v_range: is the vertical range [V]
368 """
370 self.com.write(f"CHANnel{channel}:RANGe {v_range:4.3f}")
372 def get_channel_range(self, channel: int) -> float:
373 """
374 Queries the channel range in V.
376 :param channel: is the input channel (1..4)
377 :return: channel range in V
378 """
380 return float(self.com.query(f"CHANnel{channel}:RANGe?"))
382 def set_channel_position(self, channel: int, position: float) -> None:
383 r"""
384 Sets the vertical position of the indicated channel as a graphical value.
386 * Range: -5.0 ... 5.0 [div]
387 * Increment: 0.02
388 * \*RST = 0
390 :param channel: is the channel number (1..4)
391 :param position: is the position. Positive values move the waveform up,
392 negative values move it down.
393 """
395 self.com.write(f"CHANnel{channel}:POSition {position:3.2f}")
397 def get_channel_position(self, channel: int) -> float:
398 r"""
399 Gets the vertical position of the indicated channel.
401 :param channel: is the channel number (1..4)
402 :return: channel position in div (value between -5 and 5)
403 """
405 return float(self.com.query(f"CHANnel{channel}:POSition?"))
407 def set_channel_offset(self, channel: int, offset: float) -> None:
408 r"""
409 Sets the voltage offset of the indicated channel.
411 * Range: Dependent on the channel scale and coupling [V]
412 * Increment: Minimum 0.001 [V], may be higher depending on the channel scale
413 and coupling
414 * \*RST = 0
416 :param channel: is the channel number (1..4)
417 :param offset: Offset voltage. Positive values move the waveform down,
418 negative values move it up.
419 """
421 self.com.write(f"CHANnel{channel}:OFFSet {offset:3.3f}")
423 def get_channel_offset(self, channel: int) -> float:
424 r"""
425 Gets the voltage offset of the indicated channel.
427 :param channel: is the channel number (1..4)
428 :return: channel offset voltage in V (value between -1 and 1)
429 """
431 return float(self.com.query(f"CHANnel{channel}:OFFSet?"))
433 def set_trigger_source(self, channel: int, event_type: int = 1) -> None:
434 """
435 Set the trigger (Event A) source channel.
437 :param channel: is the channel number (1..4)
438 :param event_type: is the event type. 1: A-Event, 2: B-Event, 3: R-Event
439 """
441 self.com.write(f"TRIGger{event_type}:SOURce CHAN{channel}")
443 def set_trigger_level(
444 self, channel: int, level: float, event_type: int = 1
445 ) -> None:
446 r"""
447 Sets the trigger level for the specified event and source.
449 * Range: -10 to 10 V
450 * Increment: 1e-3 V
451 * \*RST = 0 V
453 :param channel: indicates the trigger source.
455 * 1..4 = channel 1 to 4, available for all event types 1..3
456 * 5 = external trigger input on the rear panel for analog signals,
457 available for A-event type = 1
458 * 6..9 = not available
460 :param level: is the voltage for the trigger level in [V].
461 :param event_type: is the event type. 1: A-Event, 2: B-Event, 3: R-Event
462 """
464 self.com.write(f"TRIGger{event_type}:LEVel{channel} {level}")
466 def set_trigger_mode(self, mode: Union[str, TriggerModes]) -> None:
467 """
468 Sets the trigger mode which determines the behavior of the instrument if no
469 trigger occurs.
471 :param mode: is either auto, normal, or freerun.
472 :raises RTO1024Error: if an invalid triggermode is selected
473 """
475 if isinstance(mode, str):
476 try:
477 mode = self.TriggerModes[mode.upper()] # type: ignore
478 except KeyError:
479 err_msg = (
480 f'"{mode}" is not an allowed trigger mode out of '
481 f"{self.TriggerModes.names()}."
482 )
483 logging.error(err_msg)
484 raise RTO1024Error(err_msg)
485 assert isinstance(mode, self.TriggerModes)
487 self.com.write(f"TRIGger1:MODE {mode.name}")
489 def file_copy(self, source: str, destination: str) -> None:
490 """
491 Copy a file from one destination to another on the oscilloscope drive. If the
492 destination file already exists, it is overwritten without notice.
494 :param source: absolute path to the source file on the DSO filesystem
495 :param destination: absolute path to the destination file on the DSO filesystem
496 :raises RTO1024Error: if the operation did not complete
497 """
499 # clear status
500 self.com.write("*CLS")
502 # initiate file copy
503 self.com.write(f"MMEMory:COPY '{source}', '{destination}'", "*OPC")
505 # wait for OPC
506 done = self.wait_operation_complete(self.config.command_timeout_seconds)
508 if not done:
509 err_msg = "File copy not complete, timeout exceeded."
510 logging.error(err_msg)
511 raise RTO1024Error(err_msg)
513 logging.info(f'File copied: "{source}" to "{destination}"')
515 def backup_waveform(self, filename: str) -> None:
516 """
517 Backup a waveform file from the standard directory specified in the device
518 configuration to the standard backup destination specified in the device
519 configuration. The filename has to be specified without .bin or path.
521 :param filename: The waveform filename without extension and path
522 """
523 waveforms_file_path = str(PureWindowsPath(self.config.waveforms_path, filename))
524 backup_file_path = str(PureWindowsPath(self.config.backup_path, filename))
526 logging.info(f"Backing up {filename}.Wfm.bin")
527 self.file_copy(waveforms_file_path + ".Wfm.bin", backup_file_path + ".Wfm.bin")
529 logging.info(f"Backing up {filename}.bin")
530 self.file_copy(waveforms_file_path + ".bin", backup_file_path + ".bin")
532 def list_directory(self, path: str) -> List[Tuple[str, str, int]]:
533 """
534 List the contents of a given directory on the oscilloscope filesystem.
536 :param path: is the path to a folder
537 :return: a list of filenames in the given folder
538 """
540 file_string = self.com.query(f"MMEMory:CATalog? '{path}'")
542 # generate list of strings
543 file_list = re.findall('[^,^"]+,[A-Z]+,[0-9]+', file_string)
545 # delete . and .. entries
546 assert len(file_list) > 0 and file_list[0][:1] == ".", 'Expected "." folder'
547 assert len(file_list) > 1 and file_list[1][:2] == "..", 'Expected ".." folder'
548 file_list[0:2] = []
550 # split lines into lists [name, extension, size]
551 file_list = [line.split(",") for line in file_list]
553 return file_list
555 def save_waveform_history(
556 self, filename: str, channel: int, waveform: int = 1
557 ) -> None:
558 """
559 Save the history of one channel and one waveform to a .bin file. This
560 function is used after an acquisition using sequence trigger mode (with or
561 without ultra segmentation) was performed.
563 :param filename: is the name (without extension) of the file
564 :param channel: is the channel number
565 :param waveform: is the waveform number (typically 1)
566 :raises RTO1024Error: if storing waveform times out
567 """
569 # turn on fast export
570 self.com.write("EXPort:WAVeform:FASTexport ON")
572 # enable history
573 self.com.write("CHAN:HIST ON")
574 sleep(self.config.wait_sec_enable_history)
576 # turn off display
577 self.local_display(False)
579 # disable multichannel export
580 self.com.write("EXPort:WAVeform:MULTichannel OFF")
582 # select source channel and waveform
583 self.com.write(f"EXPort:WAVeform:SOURce C{channel}W{waveform}")
585 # set filename
586 abs_win_path = PureWindowsPath(self.config.waveforms_path, filename)
587 self.com.write(f"EXPort:WAVeform:NAME '{abs_win_path}.bin'")
589 # enable waveform logging
590 self.com.write("EXPort:WAVeform:DLOGging ON")
592 # clear status, to get *OPC working
593 self.com.write("*CLS")
594 sleep(self.config.wait_sec_short_pause)
596 # play waveform to start exporting
597 self.com.write("CHANnel:HISTory:PLAY", "*OPC")
598 is_done = self.wait_operation_complete(self.config.command_timeout_seconds)
600 # disable fast export
601 self.com.write("EXPort:WAVeform:FASTexport OFF")
603 # enable local display
604 self.local_display(True)
606 if not is_done:
607 logging.error("Storing waveform timed out.")
608 raise RTO1024Error("Storing waveform timed out.")
610 # check filelist
611 filenames: List[str] = [
612 file_info[0]
613 for file_info in self.list_directory(self.config.waveforms_path)
614 ]
615 if (filename + ".Wfm.bin") not in filenames or (
616 filename + ".bin"
617 ) not in filenames:
618 err_msg = f"Waveform {filename} could not be stored."
619 logging.error(err_msg)
620 raise RTO1024Error(err_msg)
622 logging.info(f"Waveform {filename} stored successfully.")
624 def run_continuous_acquisition(self) -> None:
625 """
626 Start acquiring continuously.
627 """
629 self.com.write("RUN")
631 def run_single_acquisition(self) -> None:
632 """
633 Start a single or Nx acquisition.
634 """
636 self.com.write("SINGle")
638 def stop_acquisition(self) -> None:
639 """
640 Stop any acquisition.
641 """
643 self.com.write("STOP")
645 def prepare_ultra_segmentation(self) -> None:
646 """
647 Make ready for a new acquisition in ultra segmentation mode. This function
648 does one acquisition without ultra segmentation to clear the history and
649 prepare for a new measurement.
650 """
652 # disable ultra segmentation
653 self.com.write("ACQuire:SEGMented:STATe OFF")
655 # go to AUTO trigger mode to let the scope running freely
656 self.set_trigger_mode("AUTO")
658 # pause a little bit
659 sleep(self.config.wait_sec_short_pause)
661 # start acquisition and wait for two seconds
662 self.run_continuous_acquisition()
663 sleep(self.config.wait_sec_post_acquisition_start)
665 # stop acquisition
666 self.stop_acquisition()
668 # set normal trigger mode
669 self.set_trigger_mode("NORMAL")
671 # enable ultra segmentation
672 self.com.write("ACQuire:SEGMented:STATe ON")
674 # set to maximum amount of acquisitions
675 self.com.write("ACQuire:SEGMented:MAX ON")
677 # final pause to secure the state
678 sleep(self.config.wait_sec_short_pause)
680 def save_configuration(self, filename: str) -> None:
681 r"""
682 Save the current oscilloscope settings to a file.
683 The filename has to be specified without path and '.dfl' extension, the file
684 will be saved to the configured settings directory.
686 **Information from the manual**
687 `SAVe` stores the current instrument settings under the
688 specified number in an intermediate memory. The settings can
689 be recalled using the command `\*RCL` with the associated
690 number. To transfer the stored instrument settings to a file,
691 use `MMEMory:STORe:STATe` .
693 :param filename: is the name of the settings file without path and extension
694 """
696 abs_win_path = PureWindowsPath(self.config.settings_path, filename)
697 self.com.write(f"MMEMory:SAV '{abs_win_path}.dfl'")
699 def load_configuration(self, filename: str) -> None:
700 r"""
701 Load current settings from a configuration file. The filename has to be
702 specified without base directory and '.dfl' extension.
704 **Information from the manual**
705 `ReCaLl` calls up the instrument settings from an intermediate
706 memory identified by the specified number. The instrument
707 settings can be stored to this memory using the command
708 `\*SAV` with the associated number. It also activates the
709 instrument settings which are stored in a file and loaded
710 using `MMEMory:LOAD:STATe` .
712 :param filename: is the name of the settings file without path and extension
713 """
715 abs_win_path = PureWindowsPath(self.config.settings_path, filename)
716 self.com.write(f"MMEMory:RCL '{abs_win_path}.dfl'")
718 def get_timestamps(self) -> List[float]:
719 """
720 Gets the timestamps of all recorded frames in the history and returns them as
721 a list of floats.
723 :return: list of timestamps in [s]
724 :raises RTO1024Error: if the timestamps are invalid
725 """
727 # disable local display (it is faster)
728 self.local_display(False)
730 # enable the history
731 self.com.write("CHANnel:WAVeform:HISTory:STATe 1")
733 # get the number of acquisitions
734 number_acquisitions = int(self.com.query("ACQuire:AVAilable?"))
736 # get the relative timestamp for each acquisition
737 timestamps_relative = []
739 # loop over all acquisitions. Note: Negative index up to 0!
740 for index in range(-number_acquisitions + 1, 1):
741 # switch to acquisition frame in history
742 self.com.write(f"CHANnel:WAVeform:HISTory:CURRent {index}")
744 # wait until frame is loaded
745 sleep(self.config.wait_sec_short_pause)
747 # store relative timestamp
748 timestamps_relative.append(
749 float(self.com.query("CHANnel:WAVeform:HISTory:TSRelative?"))
750 )
752 # wait until timestamp is stored
753 sleep(self.config.wait_sec_short_pause)
755 # re-enable local display
756 self.local_display(True)
758 # check validity of acquired timestamps. If they are read out too fast,
759 # there may be the same value two times in the list.
760 if len(set(timestamps_relative)) != len(timestamps_relative):
761 logging.error("Timestamps are not valid, there are doubled values.")
762 raise RTO1024Error("Timestamps are not valid, there are doubled values.")
764 logging.info("Timestamps successfully transferred.")
765 return timestamps_relative
767 def activate_measurements(
768 self,
769 meas_n: int,
770 source: str,
771 measurements: List[str],
772 category: str = "AMPTime",
773 ):
774 """
775 Activate the list of 'measurements' of the waveform 'source' in the
776 measurement box number 'meas_n'. The list 'measurements' starts with the main
777 measurement and continues with additional measurements of the same 'category'.
779 :param meas_n: measurement number 1..8
780 :param source: measurement source, for example C1W1
781 :param measurements: list of measurements, the first one will be the main
782 measurement.
783 :param category: the category of measurements, by default AMPTime
784 """
786 self.com.write(f"MEAS{meas_n}:ENAB ON")
787 self.com.write(f"MEAS{meas_n}:SOUR {source}")
788 self.com.write(f"MEAS{meas_n}:CAT {category}")
789 if measurements:
790 self.com.write(f"MEAS{meas_n}:MAIN {measurements.pop(0)}")
791 while measurements:
792 self.com.write(f"MEAS{meas_n}:ADD {measurements.pop(0)}, ON")
794 def read_measurement(self, meas_n: int, name: str) -> float:
795 """
797 :param meas_n: measurement number 1..8
798 :param name: measurement name, for example "MAX"
799 :return: measured value
800 """
802 return float(self.com.query(f"MEAS{meas_n}:RES? {name}"))