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

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

## 222 statements

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"""

4 Python module for the Rhode & Schwarz RTO 1024 oscilloscope.

5 The communication to the device is through VISA, type TCPIP / INSTR.

6"""

7

8 import logging

9 import re

10 from pathlib import PureWindowsPath

11 from time import sleep

12 from typing import List, Tuple, Union

13

14 from .visa import (

15 VisaDevice,

16 VisaDeviceConfig,

17 _VisaDeviceConfigBase,

18 _VisaDeviceConfigDefaultsBase,

19)

20 from ..comm.visa import VisaCommunication, VisaCommunicationConfig

21 from ..configuration import configdataclass

22 from ..utils.enum import AutoNumberNameEnum

23 from ..utils.typing import Number

24

25

26 class RTO1024Error(Exception):

27 pass

28

29

30@configdataclass

31 class _RTO1024ConfigBase(_VisaDeviceConfigBase):

32

33 waveforms_path: str

34 """

35 Windows directory on the oscilloscope filesystem for storing waveforms.

36 Mind escaping the backslashes of the path.

37 """

38

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 """

44

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 """

50

51

52@configdataclass

53 class _RTO1024ConfigDefaultsBase(_VisaDeviceConfigDefaultsBase):

54

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."""

67

68 def clean_values(self):

69 super().clean_values()

70

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 )

89

90

91@configdataclass

92 class RTO1024Config(VisaDeviceConfig, _RTO1024ConfigDefaultsBase, _RTO1024ConfigBase):

93 """

94 Configdataclass for the RTO1024 device.

95 """

96

97 pass

98

99

100@configdataclass

101 class RTO1024VisaCommunicationConfig(VisaCommunicationConfig):

102 """

103 Configuration dataclass for VisaCommunication with specifications for the RTO1024

104 device class.

105 """

106

107 interface_type: Union[

108 str, VisaCommunicationConfig.InterfaceType

109 ] = VisaCommunicationConfig.InterfaceType.TCPIP_INSTR # type: ignore

110

111

112 class RTO1024VisaCommunication(VisaCommunication):

113 """

114 Specialization of VisaCommunication for the RTO1024 oscilloscope

115 """

116

117 @staticmethod

118 def config_cls():

119 return RTO1024VisaCommunicationConfig

120

121

122 class RTO1024(VisaDevice):

123 """

124 Device class for the Rhode & Schwarz RTO 1024 oscilloscope.

125 """

126

127 class TriggerModes(AutoNumberNameEnum):

128 """

129 Enumeration for the three available trigger modes.

130 """

131

132 AUTO = ()

133 NORMAL = ()

134 FREERUN = ()

135

136 @classmethod

137 def names(cls):

138 """

139 Returns a list of the available trigger modes.

140 :return: list of strings

141 """

142

143 return list(map(lambda x: x.name, cls))

144

145 @staticmethod

146 def config_cls():

147 return RTO1024Config

148

149 @staticmethod

150 def default_com_cls():

151 return RTO1024VisaCommunication

152

153 def __init__(

154 self,

155 com: Union[RTO1024VisaCommunication, RTO1024VisaCommunicationConfig, dict],

156 dev_config: Union[RTO1024Config, dict],

157 ):

158 super().__init__(com, dev_config)

159

160 def start(self) -> None:

161 """

162 Start the RTO1024 oscilloscope and bring it into a defined state and remote

163 mode.

164 """

165

166 super().start()

167

168 # go to remote mode

169 self.com.write("&GTR")

170

171 # reset device (RST) and clear status registers (CLS)

172 self.com.write("*RST", "*CLS")

173

174 # enable local display

175 self.local_display(True)

176

177 # enable status register

178 self.com.write("*ESE 127")

179

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 """

185

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")

189

190 # disable any service requests SRE = 0

191 self.com.write("*SRE 0")

192

193 # device clear: abort processing of any commands

194 self.com.write("&DCL")

195

196 # go to local mode

197 self.com.write("&GTL")

198 else:

199 logging.warning("RTO1024 was already stopped")

200

201 super().stop()

202

203 def local_display(self, state: bool) -> None:

204 """

205 Enable or disable local display of the scope.

206

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}")

211

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.

216

217 * Range: 250E-12 ... 500 [s]

218 * Increment: 1E-12 [s]

219 * \*RST = 0.5 [s]

220

221 :param timerange: is the time for one acquisition. Range: 250e-12 ... 500 [s]

222 """

223

224 self.com.write(f"TIMebase:RANGe {timerange:G}")

225

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.

230

231 * Range: 250E-12 ... 500 [s]

232 * Increment: 1E-12 [s]

233

234 :return: the time for one acquisition. Range: 250e-12 ... 500 [s]

235 """

236

237 return float(self.com.query("TIMebase:RANGe?"))

238

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

244

245 * Range: 0 ... 100 [%]

246 * Increment: 1 [%]

247 * \*RST = 50 [%]

248

249 :param percentage: is the reference in %

250 """

251

252 self.com.write(f"TIMebase:REFerence {percentage:d}")

253

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

259

260 * Range: 0 ... 100 [%]

261 * Increment: 1 [%]

262

263 :return: the reference in %

264 """

265

266 return int(self.com.query("TIMebase:REFerence?"))

267

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.

272

273 * Range: 1 ... 16777215

274 * Increment: 10

275 * \*RST = 1

276

277 :param number: is the number of waveforms to acquire

278 """

279

280 self.com.write(f"ACQuire:COUNt {number:d}")

281

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.

286

287 * Range: 1 ... 16777215

288 * Increment: 10

289 * \*RST = 1

290

291 :return: the number of waveforms to acquire

292 """

293

294 return int(self.com.query("ACQuire:COUNt?"))

295

296 def set_channel_state(self, channel: int, state: bool) -> None:

297 """

298 Switches the channel signal on or off.

299

300 :param channel: is the input channel (1..4)

301 :param state: is True for on, False for off

302 """

303

304 self.com.write(f"CHANnel{channel}:STATe {'ON' if state else 'OFF'}")

305

306 def get_channel_state(self, channel: int) -> bool:

307 """

308 Queries if the channel is active or not.

309

310 :param channel: is the input channel (1..4)

311 :return: True if active, else False

312 """

313

314 return self.com.query(f"CHANnel{channel}:STATe?") == "1"

315

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.

320

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.

328

329 * Increment: 1e-3

330 * \*RST = 0.05

331

332 See also:

333 set_channel_range

334

335 :param channel: is the channel number (1..4)

336 :param scale: is the vertical scaling [V/div]

337 """

338

339 self.com.write(f"CHANnel{channel}:SCALe {scale:4.3f}")

340

341 def get_channel_scale(self, channel: int) -> float:

342 """

343 Queries the channel scale in V/div.

344

345 :param channel: is the input channel (1..4)

346 :return: channel scale in V/div

347 """

348

349 return float(self.com.query(f"CHANnel{channel}:SCALe?"))

350

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.

355

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.

362

363 * Increment: 0.01

364 * \*RST = 0.5

365

366 :param channel: is the channel number (1..4)

367 :param v_range: is the vertical range [V]

368 """

369

370 self.com.write(f"CHANnel{channel}:RANGe {v_range:4.3f}")

371

372 def get_channel_range(self, channel: int) -> float:

373 """

374 Queries the channel range in V.

375

376 :param channel: is the input channel (1..4)

377 :return: channel range in V

378 """

379

380 return float(self.com.query(f"CHANnel{channel}:RANGe?"))

381

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.

385

386 * Range: -5.0 ... 5.0 [div]

387 * Increment: 0.02

388 * \*RST = 0

389

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 """

394

395 self.com.write(f"CHANnel{channel}:POSition {position:3.2f}")

396

397 def get_channel_position(self, channel: int) -> float:

398 r"""

399 Gets the vertical position of the indicated channel.

400

401 :param channel: is the channel number (1..4)

402 :return: channel position in div (value between -5 and 5)

403 """

404

405 return float(self.com.query(f"CHANnel{channel}:POSition?"))

406

407 def set_channel_offset(self, channel: int, offset: float) -> None:

408 r"""

409 Sets the voltage offset of the indicated channel.

410

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

415

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 """

420

421 self.com.write(f"CHANnel{channel}:OFFSet {offset:3.3f}")

422

423 def get_channel_offset(self, channel: int) -> float:

424 r"""

425 Gets the voltage offset of the indicated channel.

426

427 :param channel: is the channel number (1..4)

428 :return: channel offset voltage in V (value between -1 and 1)

429 """

430

431 return float(self.com.query(f"CHANnel{channel}:OFFSet?"))

432

433 def set_trigger_source(self, channel: int, event_type: int = 1) -> None:

434 """

435 Set the trigger (Event A) source channel.

436

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 """

440

441 self.com.write(f"TRIGger{event_type}:SOURce CHAN{channel}")

442

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.

448

449 * Range: -10 to 10 V

450 * Increment: 1e-3 V

451 * \*RST = 0 V

452

453 :param channel: indicates the trigger source.

454

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

459

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 """

463

464 self.com.write(f"TRIGger{event_type}:LEVel{channel} {level}")

465

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.

470

471 :param mode: is either auto, normal, or freerun.

472 :raises RTO1024Error: if an invalid triggermode is selected

473 """

474

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)

486

487 self.com.write(f"TRIGger1:MODE {mode.name}")

488

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.

493

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 """

498

499 # clear status

500 self.com.write("*CLS")

501

502 # initiate file copy

503 self.com.write(f"MMEMory:COPY '{source}', '{destination}'", "*OPC")

504

505 # wait for OPC

506 done = self.wait_operation_complete(self.config.command_timeout_seconds)

507

508 if not done:

509 err_msg = "File copy not complete, timeout exceeded."

510 logging.error(err_msg)

511 raise RTO1024Error(err_msg)

512

513 logging.info(f'File copied: "{source}" to "{destination}"')

514

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.

520

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))

525

526 logging.info(f"Backing up {filename}.Wfm.bin")

527 self.file_copy(waveforms_file_path + ".Wfm.bin", backup_file_path + ".Wfm.bin")

528

529 logging.info(f"Backing up {filename}.bin")

530 self.file_copy(waveforms_file_path + ".bin", backup_file_path + ".bin")

531

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.

535

536 :param path: is the path to a folder

537 :return: a list of filenames in the given folder

538 """

539

540 file_string = self.com.query(f"MMEMory:CATalog? '{path}'")

541

542 # generate list of strings

543 file_list = re.findall('[^,^"]+,[A-Z]+,[0-9]+', file_string)

544

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] = []

549

550 # split lines into lists [name, extension, size]

551 file_list = [line.split(",") for line in file_list]

552

553 return file_list

554

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.

562

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 """

568

569 # turn on fast export

570 self.com.write("EXPort:WAVeform:FASTexport ON")

571

572 # enable history

573 self.com.write("CHAN:HIST ON")

574 sleep(self.config.wait_sec_enable_history)

575

576 # turn off display

577 self.local_display(False)

578

579 # disable multichannel export

580 self.com.write("EXPort:WAVeform:MULTichannel OFF")

581

582 # select source channel and waveform

583 self.com.write(f"EXPort:WAVeform:SOURce C{channel}W{waveform}")

584

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'")

588

589 # enable waveform logging

590 self.com.write("EXPort:WAVeform:DLOGging ON")

591

592 # clear status, to get *OPC working

593 self.com.write("*CLS")

594 sleep(self.config.wait_sec_short_pause)

595

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)

599

600 # disable fast export

601 self.com.write("EXPort:WAVeform:FASTexport OFF")

602

603 # enable local display

604 self.local_display(True)

605

606 if not is_done:

607 logging.error("Storing waveform timed out.")

608 raise RTO1024Error("Storing waveform timed out.")

609

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)

621

622 logging.info(f"Waveform {filename} stored successfully.")

623

624 def run_continuous_acquisition(self) -> None:

625 """

626 Start acquiring continuously.

627 """

628

629 self.com.write("RUN")

630

631 def run_single_acquisition(self) -> None:

632 """

633 Start a single or Nx acquisition.

634 """

635

636 self.com.write("SINGle")

637

638 def stop_acquisition(self) -> None:

639 """

640 Stop any acquisition.

641 """

642

643 self.com.write("STOP")

644

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 """

651

652 # disable ultra segmentation

653 self.com.write("ACQuire:SEGMented:STATe OFF")

654

655 # go to AUTO trigger mode to let the scope running freely

656 self.set_trigger_mode("AUTO")

657

658 # pause a little bit

659 sleep(self.config.wait_sec_short_pause)

660

661 # start acquisition and wait for two seconds

662 self.run_continuous_acquisition()

663 sleep(self.config.wait_sec_post_acquisition_start)

664

665 # stop acquisition

666 self.stop_acquisition()

667

668 # set normal trigger mode

669 self.set_trigger_mode("NORMAL")

670

671 # enable ultra segmentation

672 self.com.write("ACQuire:SEGMented:STATe ON")

673

674 # set to maximum amount of acquisitions

675 self.com.write("ACQuire:SEGMented:MAX ON")

676

677 # final pause to secure the state

678 sleep(self.config.wait_sec_short_pause)

679

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.

685

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` .

692

693 :param filename: is the name of the settings file without path and extension

694 """

695

696 abs_win_path = PureWindowsPath(self.config.settings_path, filename)

697 self.com.write(f"MMEMory:SAV '{abs_win_path}.dfl'")

698

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.

703

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` .

711

712 :param filename: is the name of the settings file without path and extension

713 """

714

715 abs_win_path = PureWindowsPath(self.config.settings_path, filename)

716 self.com.write(f"MMEMory:RCL '{abs_win_path}.dfl'")

717

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.

722

723 :return: list of timestamps in [s]

724 :raises RTO1024Error: if the timestamps are invalid

725 """

726

727 # disable local display (it is faster)

728 self.local_display(False)

729

730 # enable the history

731 self.com.write("CHANnel:WAVeform:HISTory:STATe 1")

732

733 # get the number of acquisitions

734 number_acquisitions = int(self.com.query("ACQuire:AVAilable?"))

735

736 # get the relative timestamp for each acquisition

737 timestamps_relative = []

738

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}")

743

744 # wait until frame is loaded

745 sleep(self.config.wait_sec_short_pause)

746

747 # store relative timestamp

748 timestamps_relative.append(

749 float(self.com.query("CHANnel:WAVeform:HISTory:TSRelative?"))

750 )

751

752 # wait until timestamp is stored

753 sleep(self.config.wait_sec_short_pause)

754

755 # re-enable local display

756 self.local_display(True)

757

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.")

763

764 logging.info("Timestamps successfully transferred.")

765 return timestamps_relative

766

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'.

778

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 """

785

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")

793

794 def read_measurement(self, meas_n: int, name: str) -> float:

795 """

796

797 :param meas_n: measurement number 1..8

798 :param name: measurement name, for example "MAX"

799 :return: measured value

800 """

801

802 return float(self.com.query(f"MEAS{meas_n}:RES? {name}"))