Skip to content

Commit de86941

Browse files
author
Henri Ervasti
committed
[184-add-new-functions-to-yokogawa-driver] # Started adding new functions. Still need to think how the <block data> format should be handled when obtaining trace data in a format that returns <block data>.
1 parent d87a6cc commit de86941

File tree

1 file changed

+144
-26
lines changed

1 file changed

+144
-26
lines changed

qmi/instruments/yokogawa/dlm4038.py

Lines changed: 144 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ def _channels_check(self, channels: int | list[int] | str) -> int | list[int]:
6161
assert not isinstance(channels, str)
6262
return channels
6363

64+
@staticmethod
65+
def _data_format_check(data_format: str) -> str:
66+
"""Check the data format string and return in SCPI style."""
67+
data_formats = {
68+
"ascii": "ASCii",
69+
"byte": "BYTE",
70+
"rbyte": "RBYTe",
71+
"word": "WORD"
72+
}
73+
if data_format.lower() not in data_formats:
74+
raise QMI_InstrumentException(f"Unexpected data format, got {data_format}")
75+
76+
return data_formats[data_format.lower()]
77+
6478
@staticmethod
6579
def _data_type_check(data_type: str) -> str:
6680
"""Check the data type string and return in SCPI style."""
@@ -70,6 +84,7 @@ def _data_type_check(data_type: str) -> str:
7084
data_type = "ASCii"
7185
else:
7286
raise QMI_InstrumentException(f"Unexpected data type, got {data_type}")
87+
7388
return data_type
7489

7590
def _channel_value_setter(
@@ -280,17 +295,13 @@ def get_max_waveform(self, channels: int | list[int] | str) -> np.ndarray:
280295

281296
@rpc_method
282297
def set_channel_position(self, channels: int | list[int] | str, position: float | list[float]) -> None:
283-
"""
284-
Changes the position of the specified channels in volts.
285-
"""
298+
"""Changes the position of the specified channels in volts."""
286299
checked_chs = self._channels_check(channels)
287300
self._channel_value_setter(checked_chs, position, "POSition")
288301

289302
@rpc_method
290303
def get_channel_position(self, channels: int | list[int] | str) -> np.ndarray:
291-
"""
292-
Returns the current position for the specified channels in volts.
293-
"""
304+
"""Returns the current position for the specified channels in volts."""
294305
checked_chs = self._channels_check(channels)
295306
position = self._channel_value_getter(checked_chs, "POSition", 11)
296307

@@ -314,6 +325,21 @@ def set_trigger_level(self, voltage: float) -> None:
314325
"""
315326
self._scpi_protocol.write(f":TRIGger:ATRigger:SIMPle:LEVel {voltage}V")
316327

328+
@rpc_method
329+
def set_average(self, average: int) -> None:
330+
"""Sets the mode to average and set the average count.
331+
332+
Parameters:
333+
average: Has to be between 2 and 1024 in 2**n steps.
334+
"""
335+
self._scpi_protocol.write(":ACQUIRE:MODE AVERAGE")
336+
self._scpi_protocol.write(f":ACQUIRE:AVERAGE:COUNT {average}")
337+
338+
@rpc_method
339+
def set_normal(self) -> None:
340+
"""Sets acquisition mode to normal."""
341+
self._scpi_protocol.write(":ACQUIRE:MODE NORMAL")
342+
317343
@rpc_method
318344
def set_number_data_points(self, length: int) -> None:
319345
"""Sets the scope acquire length.
@@ -325,49 +351,141 @@ def set_number_data_points(self, length: int) -> None:
325351

326352
@rpc_method
327353
def get_number_data_points(self) -> int:
354+
"""Gets the number of data points that will be saved."""
355+
return int(self._scpi_protocol.ask(":WAVeform:LENGth?")[10:])
356+
357+
@rpc_method
358+
def set_data_format(self, data_format: str) -> None:
359+
"""Set the waveform data format to specific type.
360+
361+
Parameters:
362+
data_format: What format to send the data in. Possible options:
328363
"""
329-
Gets the number of data points that will be saved.
330-
"""
331-
return int(self._scpi_protocol.ask(":WAVeform:LENGth?")[10:]) # TODO: range 10:-1 needs to be checked with HW
364+
data_format = self._data_format_check(data_format)
365+
self._scpi_protocol.write(":WAVeform:FORMat {data_format}")
332366

333367
@rpc_method
334-
def set_average(self, average: int) -> None:
335-
"""Sets the mode to average and set the average count.
368+
def set_waveform_trace_channel(self, channel: int) -> None:
369+
"""Set the waveform (channel) number to obtain the waveform trace data from.
370+
371+
Parameters:
372+
channel: Waveform (channel) number.
373+
"""
374+
assert channel in range(1, self.CHANNELS + 1), f"Invalid waveform channel number {channel}"
375+
self._scpi_protocol.write(f":WAVeform:TRACE {channel}")
336376

337-
Parameters:
338-
average: Has to be between 2 and 1024 in 2**n steps.
377+
@rpc_method
378+
def get_waveform_trace_data(self, data_format: str) -> np.ndarray:
379+
"""Get waveform (channel) trace data from single waveform.
380+
381+
Returns:
382+
trace_data: The trace data formatted according to the given data format.
339383
"""
340-
self._scpi_protocol.write(":ACQUIRE:MODE AVERAGE")
341-
self._scpi_protocol.write(f":ACQUIRE:AVERAGE:COUNT {average}")
384+
data_format = self._data_format_check(data_format)
385+
# Get raw trace data
386+
raw_data = self._scpi_protocol.ask(":WAVeform:SEND?")
387+
# Manipulate and return
388+
if data_format == "ASCii":
389+
return np.array([float(x) for x in raw_data.split(',')])
390+
391+
'''<Block Data>
392+
<Block data> is any 8-bit data. It is only used in
393+
response messages on the DLM4000. The syntax is as
394+
follows:
395+
Form Example
396+
#N<N-digit decimal number><data byte sequence>#800000010ABCDEFGHIJ
397+
• #N
398+
Indicates that the data is <block data>. “N” indicates
399+
the number of succeeding data bytes (digits) in
400+
ASCII code.
401+
• <N-digit decimal number>
402+
Indicates the number of bytes of data (example:
403+
00000010 = 10 bytes).
404+
• <Data byte sequence>
405+
Expresses the actual data (example: ABCDEFGHIJ).
406+
• Data is comprised of 8-bit values (0 to 255). This
407+
means that the ASCII code “0AH,” which stands for
408+
“NL,” can also be included in the data. Hence, care
409+
must be taken when programming the controller'''
410+
range = self.get_waveform_data_range() # TODO
411+
offset = self.get_waveform_data_offset() # TODO
412+
position = 0.0
413+
if data_format == "WORD":
414+
division = 3200.0
415+
416+
elif data_format == "BYTE":
417+
division = 12.5
418+
419+
elif data_format == "RBYTe":
420+
division = 25.0
421+
position = self.get_waveform_position() # TODO
422+
423+
data_points = ...
424+
for dp in data_points:
425+
value = range * (dp - position) / division + offset
342426

343427
@rpc_method
344-
def set_normal(self) -> None:
345-
"""Sets acquisition mode to normal."""
346-
self._scpi_protocol.write(":ACQUIRE:MODE NORMAL")
428+
def get_waveforms_trace_data(self, channels: list[int], data_format: str = "ascii") -> np.ndarray:
429+
"""Returns the on-screen traces from specified waveforms (channels) in the form of a 2D numpy array.
430+
Data is streamed via ethernet in the given format.
431+
432+
Parameters:
433+
channels: A list of the waveform numbers (waveform channels) to return data from.
434+
data_format: What format to send the data in. Possible formats are:
435+
'ascii' | 'byte' | 'rbyte' | 'word'. Default is 'ascii'
436+
437+
Returns:
438+
traces: An array where each row is a channel trace.
439+
Channel order corresponds to 'channels' parameter.
440+
"""
441+
# Expected length of each trace
442+
num_points = self.get_number_data_points()
443+
444+
# Initialize data array
445+
traces = np.zeros((len(channels), num_points))
446+
447+
# Stop to acquire data
448+
self.stop()
449+
450+
# Set data format
451+
self.set_data_format(data_format)
347452

453+
# Get trace data from each defined channel
454+
for i, waveform in enumerate(channels):
455+
# Set which channel to get data from
456+
self.set_waveform_trace_channel(waveform)
457+
# Transfer data from the stopped acquisition
458+
resp = self.get_waveform_trace_data(data_format)
459+
traces[i] = np.array([float(x) for x in resp.split(',')])
460+
461+
# Start continuous acquisition again
462+
self.start()
463+
464+
return traces
465+
348466
@rpc_method
349467
def save_file(self, name: str, data_type: str = "binary", waiting_time: float = 12.0) -> None:
350468
"""Saves in the internal memory the file with a name "nameXXX.csv".
351469
Here XXX labels the files that have the same name with numbers from 000 to 999.
352470
353471
Parameters:
354472
name: The preposition of the file name.
355-
data_type: Specify with type: 'binary' for raw data and 'ascii' for csv data.
473+
data_type: Specify data type: 'binary' for raw data and 'ascii' for csv data.
356474
waiting_time: Time to wait for saving the file. In seconds.
357475
"""
358476
data_type = self._data_type_check(data_type)
359477

360478
# Stop in order to acquire the data
361-
self._scpi_protocol.write(":STOP")
479+
self.stop()
362480
# Parameters of the saved file
363-
self._scpi_protocol.write(":WAV:FORM BYTE")
481+
self.set_data_format("byte")
364482
_ = self.get_number_data_points()
365483
self._scpi_protocol.write(f":FILE:SAVE:NAME {name}")
366484
self._scpi_protocol.write(f":FILE:SAVE:{data_type}:EXECute")
367485
# waits until the save is completed
368486
time.sleep(waiting_time)
369487
# Starts again, notice that at least a time of 10*time_division is needed to get a full spectrum after starting
370-
self._scpi_protocol.write(":START")
488+
self.start()
371489

372490
@rpc_method
373491
def find_file_name(self, name: str, select_files: str = "last") -> list[str]:
@@ -428,23 +546,23 @@ def copy_file(self, name: str, destination: str, select_files: str = "last") ->
428546
shutil.copy(f"{self._directory_oscilloscope}{f}", destination)
429547

430548
@rpc_method
431-
def delete_file(self, name: str, select_files: str = "last", data_type: str = "binary") -> None:
549+
def delete_file(self, name: str, select_files: str = "last", data_format: str = "binary") -> None:
432550
"""Deletes files in the oscilloscope with names 'nameXXX.csv'.
433551
It can be chosen whether to delete the last file created with that name (higher XXX) with select_files = 'last'
434552
or to delete all of them with select_files = 'all'.
435553
436554
Parameters:
437555
name: Preposition of file names to find.
438556
select_files: Optional parameter to select only the "last" file (default) or "all" the files.
439-
data_type: Specify with type: 'binary' for raw data and 'ascii' for csv data file.
557+
data_format: Specify with type: 'binary' for raw data and 'ascii' for csv data file.
440558
"""
441-
data_type = self._data_type_check(data_type)
559+
data_format = self._data_format_check(data_format)
442560

443561
# Stops in order to delete the data
444562
self.stop()
445563
file_names = self.find_file_name(name, select_files)
446564
for f in file_names:
447-
self._scpi_protocol.write(f':FILE:DELete:{data_type}:EXECute "{f[:-4]}"')
565+
self._scpi_protocol.write(f':FILE:DELete:{data_format}:EXECute "{f[:-4]}"')
448566

449567
# Starts again, notice that at least a time of 10*time_division is needed to get a full spectrum after starting
450568
self.start()

0 commit comments

Comments
 (0)