Hide keyboard shortcuts

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

7 

8import logging 

9import re 

10from pathlib import PureWindowsPath 

11from time import sleep 

12from typing import List, Tuple, Union 

13 

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 

24 

25 

26class RTO1024Error(Exception): 

27 pass 

28 

29 

30@configdataclass 

31class _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 

53class _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 

92class RTO1024Config(VisaDeviceConfig, _RTO1024ConfigDefaultsBase, _RTO1024ConfigBase): 

93 """ 

94 Configdataclass for the RTO1024 device. 

95 """ 

96 

97 pass 

98 

99 

100@configdataclass 

101class 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 

112class RTO1024VisaCommunication(VisaCommunication): 

113 """ 

114 Specialization of VisaCommunication for the RTO1024 oscilloscope 

115 """ 

116 

117 @staticmethod 

118 def config_cls(): 

119 return RTO1024VisaCommunicationConfig 

120 

121 

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