keithley-tempcontrol 2025.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,102 @@
1
+ BaseClass:
2
+ egse.tempcontrol.keithley.daq6510.DAQ6510Interface
3
+ ProxyClass:
4
+ egse.tempcontrol.keithley.daq6510.DAQ6510Proxy
5
+ ControlServerClass:
6
+ egse.tempcontrol.keithley.daq6510_cs.DAQ6510ControlServer
7
+ ControlServer:
8
+ egse.tempcontrol.keithley.daq6510_cs
9
+
10
+ Commands:
11
+
12
+ # Each of these groups is parsed and used on both the server and the client side.
13
+ # The group name (e.g. is_simulator) will be monkey-patched in the Proxy class for the device or service.
14
+ # The other fields are:
15
+ # description: Used by the doc_string method to generate a help string
16
+ # cmd: Command string that will eventually be sent to the hardware controller for the
17
+ # device after the arguments have been filled.
18
+ # device_method: The name of the method to be called on the device class.
19
+ # These should all be defined by the base class for the device, i.e. KeithleyBase.
20
+ # response: The name of the method to be called from the device protocol.
21
+ # This method should exist in the sub-class of the CommandProtocol base class, i.e.
22
+ # in this case it will be the KeithleyProtocol class.
23
+
24
+ # Definition of the DeviceInterface
25
+
26
+ disconnect:
27
+ description : Disconnects from the Keithley controller. This command will be sent to the
28
+ Keithley Control Server which will then disconnect from the hardware controller.
29
+ This command doesn't affect the ZeroMQ connection of this Proxy to the
30
+ control server. Use the service command ``disconnect()`` to disconnect
31
+ from the control server.
32
+
33
+ connect:
34
+ description: Connects the Keithley hardware controller
35
+
36
+ reconnect:
37
+ description: Reconnects the Keithley hardware controller.
38
+
39
+ This command will force a disconnect and then try to re-connect to the controller.
40
+
41
+ is_simulator:
42
+ description: Asks if the control server is a simulator instead of the real KeithleyController class.
43
+ returns: bool | True if the far end is a simulator instead of the real hardware
44
+
45
+ is_connected:
46
+ description: Checks if the Keithley Hardware Controller is connected.
47
+
48
+
49
+ # Definition of the device commands
50
+
51
+ info:
52
+ description: Retrieves basic information about the Keithley and the Controller.
53
+
54
+ reset:
55
+ description: Resets the device. This returns the instrument to default settings, and cancels all
56
+ pending commands.
57
+
58
+ send_command:
59
+ description: Sends a SCPI command to the device
60
+ cmd: '{command} {response}'
61
+
62
+ set_time:
63
+ description: Sets the absolute date and time of the device.
64
+ cmd: '{year} {month} {day} {hour} {minute} {second}'
65
+
66
+ get_time:
67
+ description: Gets the time and time of the device.
68
+
69
+ read_buffer:
70
+ description: Reads specific data elements (measurements) from the given buffer.
71
+
72
+ get_buffer_count:
73
+ description: The number of readings in the specified reading buffer.
74
+
75
+ get_buffer_capacity:
76
+ description: The total number of readings that the buffer can store.
77
+
78
+ delete_buffer:
79
+ description: Deletes the given buffer.
80
+
81
+ clear_buffer:
82
+ description: Clears all readings and statistics from the specified buffer.
83
+
84
+ create_buffer:
85
+ description: Creates a Reading Buffer with the given name.
86
+
87
+ configure_sensors:
88
+ description: Allows to configure the different sensors in the `channel_list`. Each sensor
89
+ in the list will be configured according to the settings given in the
90
+ `sense` dictionary.
91
+ cmd: '{channel_list} {sense}'
92
+
93
+ setup_measurements:
94
+ description: Sets up the measurements for the given channel list.
95
+ cmd: '{channel_list}'
96
+
97
+ perform_measurement:
98
+ description: Performs the actual measurements. This function will wait until all
99
+ measurements have completed, so be careful with the arguments `count` and
100
+ `interval` as they will multiply into the number of seconds that you will
101
+ have to wait for the response.
102
+ cmd: '{channel_list} {count} {interval}'
@@ -0,0 +1,197 @@
1
+ import logging
2
+
3
+ from prometheus_client import start_http_server
4
+
5
+ import multiprocessing
6
+ multiprocessing.current_process().name = "daq6510_cs"
7
+
8
+ import sys
9
+
10
+ import click
11
+ import invoke
12
+ import rich
13
+ import zmq
14
+
15
+ from egse.control import ControlServer
16
+ from egse.control import is_control_server_active
17
+ from egse.settings import Settings
18
+ from egse.tempcontrol.keithley.daq6510 import DAQ6510Proxy
19
+ from egse.tempcontrol.keithley.daq6510_protocol import DAQ6510Protocol
20
+ from egse.zmq_ser import connect_address
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ CTRL_SETTINGS = Settings.load("Keithley Control Server")
25
+
26
+
27
+ def is_daq6510_cs_active(timeout: float = 0.5) -> bool:
28
+ """ Checks if the DAQ6510 Control Server is running.
29
+
30
+ Args:
31
+ timeout (float): Timeout when waiting for a reply [s, default=0.5]
32
+
33
+ Returns: True if the Control Server is running and replied with the expected answer; False otherwise.
34
+ """
35
+
36
+ endpoint = connect_address(
37
+ CTRL_SETTINGS.PROTOCOL, CTRL_SETTINGS.HOSTNAME, CTRL_SETTINGS.COMMANDING_PORT
38
+ )
39
+
40
+ return is_control_server_active(endpoint, timeout)
41
+
42
+
43
+ class DAQ6510ControlServer(ControlServer):
44
+ """
45
+ Keithley DAQ6510ControlServer - Command and monitor the Keithley Data Acquisition System.
46
+
47
+ This class works as a command and monitoring server to control the DAQ6510 Controller.
48
+
49
+ The sever binds to the following ZeroMQ sockets:
50
+
51
+ - REQ-REP socket that can be used as a command server. Any client can connect and send a command to the
52
+ DAQ6510 controller.
53
+
54
+ - PUB-SUP socket that serves as a monitoring server. It will send out DAQ6510 status information to all the
55
+ connected clients every DELAY seconds.
56
+
57
+ """
58
+
59
+ def __init__(self):
60
+ """ Initialisation of a DAQ6510 Control Server."""
61
+
62
+ super().__init__()
63
+
64
+ self.device_protocol = DAQ6510Protocol(self)
65
+
66
+ self.logger.info(f"Binding ZeroMQ socket to {self.device_protocol.get_bind_address()}")
67
+
68
+ self.device_protocol.bind(self.dev_ctrl_cmd_sock)
69
+
70
+ self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)
71
+
72
+ def get_communication_protocol(self) -> str:
73
+ """ Returns the communication protocol used by the Control Server.
74
+
75
+ Returns: Communication protocol used by the Control Server, as specified in the settings.
76
+ """
77
+
78
+ return CTRL_SETTINGS.PROTOCOL
79
+
80
+ def get_commanding_port(self) -> int:
81
+ """ Returns the commanding port used by the Control Server.
82
+
83
+ Returns: Commanding port used by the Control Server, as specified in the settings.
84
+ """
85
+
86
+ return CTRL_SETTINGS.COMMANDING_PORT
87
+
88
+ def get_service_port(self):
89
+ """ Returns the service port used by the Control Server.
90
+
91
+ Returns: Service port used by the Control Server, as specified in the settings.
92
+ """
93
+
94
+ return CTRL_SETTINGS.SERVICE_PORT
95
+
96
+ def get_monitoring_port(self):
97
+ """ Returns the monitoring port used by the Control Server.
98
+
99
+ Returns: Monitoring port used by the Control Server, as specified in the settings.
100
+ """
101
+
102
+ return CTRL_SETTINGS.MONITORING_PORT
103
+
104
+ def get_storage_mnemonic(self):
105
+ """ Returns the storage mnemonics used by the Control Server.
106
+
107
+ This is a string that will appear in the filename with the housekeeping information of the device, as a way of
108
+ identifying the device. If this is not implemented in the sub-class, then the class name will be used.
109
+
110
+ Returns: Storage mnemonics used by the Control Server, as specified in the settings. If not specified in the
111
+ settings, "DAQ6510" will be used.
112
+ """
113
+
114
+ try:
115
+ return CTRL_SETTINGS.STORAGE_MNEMONIC
116
+ except AttributeError:
117
+ return "DAQ6510"
118
+
119
+ def before_serve(self):
120
+ """ Steps to take before the Control Server is activated."""
121
+
122
+ start_http_server(CTRL_SETTINGS.METRICS_PORT)
123
+
124
+
125
+ @click.group()
126
+ def cli():
127
+ pass
128
+
129
+
130
+ @cli.command()
131
+ def start():
132
+ """ Starts the Keithley DAQ6510 Control Server."""
133
+
134
+ try:
135
+ control_server = DAQ6510ControlServer()
136
+ control_server.serve()
137
+ except KeyboardInterrupt:
138
+ logger.debug("Shutdown requested...exiting")
139
+ except SystemExit as exit_code:
140
+ logger.debug("System Exit with code {}.".format(exit_code))
141
+ sys.exit(exit_code)
142
+ except Exception:
143
+ msg = "Cannot start the DAQ6510 Control Server"
144
+ logger.exception(msg)
145
+ rich.print(f"[red]{msg}.")
146
+
147
+ return 0
148
+
149
+
150
+ @cli.command()
151
+ def start_bg():
152
+ """ Starts the DAQ6510 Control Server in the background."""
153
+
154
+ invoke.run("daq6510_cs start", disown=True)
155
+
156
+
157
+ @cli.command()
158
+ def stop():
159
+ """ Sends a 'quit_server' command to the Keithley DAQ6510 Control Server."""
160
+
161
+ try:
162
+ with DAQ6510Proxy() as daq:
163
+ sp = daq.get_service_proxy()
164
+ sp.quit_server()
165
+ except ConnectionError as exc:
166
+ msg = "Cannot stop the DAQ6510 Control Server"
167
+ logger.exception(msg)
168
+ rich.print(f"[red]{msg}, could not send the Quit command. [black]Check log messages.")
169
+
170
+
171
+ @cli.command()
172
+ def status():
173
+ """ Requests status information from the Control Server."""
174
+
175
+ protocol = CTRL_SETTINGS.PROTOCOL
176
+ hostname = CTRL_SETTINGS.HOSTNAME
177
+ port = CTRL_SETTINGS.COMMANDING_PORT
178
+
179
+ endpoint = connect_address(protocol, hostname, port)
180
+
181
+ if is_control_server_active(endpoint):
182
+ rich.print(f"DAQ6510 CS: [green]active")
183
+ with DAQ6510Proxy() as daq6510:
184
+ sim = daq6510.is_simulator()
185
+ connected = daq6510.is_connected()
186
+ ip = daq6510.get_ip_address()
187
+ rich.print(f"mode: {'simulator' if sim else 'device'}{' not' if not connected else ''} connected")
188
+ rich.print(f"hostname: {ip}")
189
+ else:
190
+ rich.print(f"DAQ6510 CS: [red]not active")
191
+
192
+
193
+ if __name__ == "__main__":
194
+
195
+ logging.basicConfig(level=logging.DEBUG, format=Settings.LOG_FORMAT_FULL)
196
+
197
+ sys.exit(cli())
@@ -0,0 +1,359 @@
1
+ import logging
2
+ import socket
3
+ import time
4
+
5
+ from egse.command import ClientServerCommand
6
+ from egse.device import DeviceConnectionError
7
+ from egse.device import DeviceConnectionInterface
8
+ from egse.device import DeviceError
9
+ from egse.device import DeviceTimeoutError
10
+ from egse.device import DeviceTransport
11
+ from egse.settings import Settings
12
+ from egse.system import Timer
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ IDENTIFICATION_QUERY = "*IDN?"
17
+
18
+ DEVICE_SETTINGS = Settings.load("Keithley DAQ6510")
19
+ DEVICE_NAME = "DAQ6510"
20
+ READ_TIMEOUT = DEVICE_SETTINGS.TIMEOUT # [s], can be smaller than timeout (for DAQ6510Proxy) (e.g. 1s)
21
+
22
+
23
+ class DAQ6510Command(ClientServerCommand):
24
+
25
+ def get_cmd_string(self, *args, **kwargs) -> str:
26
+ """ Constructs the command string, based on the given positional and/or keyword arguments.
27
+
28
+ Args:
29
+ *args: Positional arguments that are needed to construct the command string
30
+ **kwargs: Keyword arguments that are needed to construct the command string
31
+
32
+ Returns: Command string with the given positional and/or keyword arguments filled out.
33
+ """
34
+
35
+ out = super().get_cmd_string(*args, **kwargs)
36
+ return out + "\n"
37
+
38
+
39
+ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
40
+ """ Defines the low-level interface to the Keithley DAQ6510 Controller."""
41
+
42
+ def __init__(self, hostname: str = None, port: int = None):
43
+ """ Initialisation of an Ethernet interface for the DAQ6510.
44
+
45
+ Args:
46
+ hostname(str): Hostname to which to open a socket
47
+ port (int): Port to which to open a socket
48
+ """
49
+
50
+ super().__init__()
51
+
52
+ self.hostname = DEVICE_SETTINGS.HOSTNAME if hostname is None else hostname
53
+ self.port = DEVICE_SETTINGS.PORT if port is None else port
54
+ self.sock = None
55
+
56
+ self.is_connection_open = False
57
+
58
+ def connect(self):
59
+ """ Connects the device.
60
+
61
+ Raises:
62
+ DeviceConnectionError: When the connection could not be established. Check the logging messages for more
63
+ details.
64
+ DeviceTimeoutError: When the connection timed out.
65
+ ValueError: When hostname or port number are not provided.
66
+ """
67
+
68
+ # Sanity checks
69
+
70
+ if self.is_connection_open:
71
+ logger.warning(f"{DEVICE_NAME}: trying to connect to an already connected socket.")
72
+ return
73
+
74
+ if self.hostname in (None, ""):
75
+ raise ValueError(f"{DEVICE_NAME}: hostname is not initialized.")
76
+
77
+ if self.port in (None, 0):
78
+ raise ValueError(f"{DEVICE_NAME}: port number is not initialized.")
79
+
80
+ # Create a new socket instance
81
+
82
+ try:
83
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
84
+ # The following lines are to experiment with blocking and timeout, but there is no need.
85
+ # self.sock.setblocking(1)
86
+ # self.sock.settimeout(3)
87
+ except socket.error as e_socket:
88
+ raise DeviceConnectionError(DEVICE_NAME, "Failed to create socket.") from e_socket
89
+
90
+ # Attempt to establish a connection to the remote host
91
+
92
+ # FIXME: Socket shall be closed on exception?
93
+
94
+ # We set a timeout of 3s before connecting and reset to None (=blocking) after the `connect` method has been
95
+ # called. This is because when no device is available, e.g. during testing, the timeout will take about
96
+ # two minutes, which is way too long. It needs to be evaluated if this approach is acceptable and not causing
97
+ # problems during production.
98
+
99
+ try:
100
+ logger.debug(f'Connecting a socket to host "{self.hostname}" using port {self.port}')
101
+ self.sock.settimeout(3)
102
+ self.sock.connect((self.hostname, self.port))
103
+ self.sock.settimeout(None)
104
+ except ConnectionRefusedError as exc:
105
+ raise DeviceConnectionError(
106
+ DEVICE_NAME, f"Connection refused to {self.hostname}:{self.port}."
107
+ ) from exc
108
+ except TimeoutError as exc:
109
+ raise DeviceTimeoutError(
110
+ DEVICE_NAME, f"Connection to {self.hostname}:{self.port} timed out."
111
+ ) from exc
112
+ except socket.gaierror as exc:
113
+ raise DeviceConnectionError(
114
+ DEVICE_NAME, f"Socket address info error for {self.hostname}"
115
+ ) from exc
116
+ except socket.herror as exc:
117
+ raise DeviceConnectionError(
118
+ DEVICE_NAME, f"Socket host address error for {self.hostname}"
119
+ ) from exc
120
+ except socket.timeout as exc:
121
+ raise DeviceTimeoutError(
122
+ DEVICE_NAME, f"Socket timeout error for {self.hostname}:{self.port}"
123
+ ) from exc
124
+ except OSError as exc:
125
+ raise DeviceConnectionError(DEVICE_NAME, f"OSError caught ({exc}).") from exc
126
+
127
+ self.is_connection_open = True
128
+
129
+ # Check that we are connected to the controller by issuing the "VERSION" or
130
+ # "*ISDN?" query. If we don't get the right response, then disconnect automatically.
131
+
132
+ if not self.is_connected():
133
+ raise DeviceConnectionError(
134
+ DEVICE_NAME, "Device is not connected, check logging messages for the cause."
135
+ )
136
+
137
+ def disconnect(self):
138
+ """ Disconnects from the Ethernet connection.
139
+
140
+ Raises:
141
+ DeviceConnectionError when the socket could not be closed.
142
+ """
143
+
144
+ try:
145
+ if self.is_connection_open:
146
+ logger.debug(f"Disconnecting from {self.hostname}")
147
+ self.sock.close()
148
+ self.is_connection_open = False
149
+ except Exception as e_exc:
150
+ raise DeviceConnectionError(
151
+ DEVICE_NAME, f"Could not close socket to {self.hostname}") from e_exc
152
+
153
+ def reconnect(self):
154
+ """ Reconnects to the device controller.
155
+
156
+ Raises:
157
+ ConnectionError when the device cannot be reconnected for some reason.
158
+ """
159
+
160
+ if self.is_connection_open:
161
+ self.disconnect()
162
+ self.connect()
163
+
164
+ def is_connected(self) -> bool:
165
+ """ Checks if the device is connected.
166
+
167
+ This will send a query for the device identification and validate the answer.
168
+
169
+ Returns: True is the device is connected and answered with the proper ID; False otherwise.
170
+ """
171
+
172
+ if not self.is_connection_open:
173
+ return False
174
+
175
+ try:
176
+ version = self.query(IDENTIFICATION_QUERY)
177
+ except DeviceError as exc:
178
+ logger.exception(exc)
179
+ logger.error("Most probably the client connection was closed. Disconnecting...")
180
+ self.disconnect()
181
+ return False
182
+
183
+ if "DAQ6510" not in version:
184
+ logger.error(
185
+ f'Device did not respond correctly to a "VERSION" command, response={version}. '
186
+ f"Disconnecting..."
187
+ )
188
+ self.disconnect()
189
+ return False
190
+
191
+ return True
192
+
193
+ def write(self, command: str):
194
+ """ Senda a single command to the device controller without waiting for a response.
195
+
196
+ Args:
197
+ command (str): Command to send to the controller
198
+
199
+ Raises:
200
+ DeviceConnectionError when the command could not be sent due to a communication problem.
201
+ DeviceTimeoutError when the command could not be sent due to a timeout.
202
+ """
203
+
204
+ try:
205
+ command += "\n" if not command.endswith("\n") else ""
206
+
207
+ self.sock.sendall(command.encode())
208
+
209
+ except socket.timeout as e_timeout:
210
+ raise DeviceTimeoutError(DEVICE_NAME, "Socket timeout error") from e_timeout
211
+ except socket.error as e_socket:
212
+ # Interpret any socket-related error as a connection error
213
+ raise DeviceConnectionError(DEVICE_NAME, "Socket communication error.") from e_socket
214
+ except AttributeError:
215
+ if not self.is_connection_open:
216
+ msg = "The DAQ6510 is not connected, use the connect() method."
217
+ raise DeviceConnectionError(DEVICE_NAME, msg)
218
+ raise
219
+
220
+ def trans(self, command: str) -> str:
221
+ """ Sends a single command to the device controller and block until a response from the controller.
222
+
223
+ This is seen as a transaction.
224
+
225
+ Args:
226
+ command (str): Command to send to the controller
227
+
228
+ Returns:
229
+ Either a string returned by the controller (on success), or an error message (on failure).
230
+
231
+ Raises:
232
+ DeviceConnectionError when there was an I/O problem during communication with the controller.
233
+ DeviceTimeoutError when there was a timeout in either sending the command or receiving the response.
234
+ """
235
+
236
+ try:
237
+ # Attempt to send the complete command
238
+
239
+ command += "\n" if not command.endswith("\n") else ""
240
+
241
+ self.sock.sendall(command.encode())
242
+
243
+ # wait for, read and return the response from HUBER (will be at most TBD chars)
244
+
245
+ return_string = self.read()
246
+
247
+ return return_string.decode().rstrip()
248
+
249
+ except socket.timeout as e_timeout:
250
+ raise DeviceTimeoutError(DEVICE_NAME, "Socket timeout error") from e_timeout
251
+ except socket.error as e_socket:
252
+ # Interpret any socket-related error as an I/O error
253
+ raise DeviceConnectionError(DEVICE_NAME, "Socket communication error.") from e_socket
254
+ except ConnectionError as exc:
255
+ raise DeviceConnectionError(DEVICE_NAME, "Connection error.") from exc
256
+ except AttributeError:
257
+ if not self.is_connection_open:
258
+ raise DeviceConnectionError(
259
+ DEVICE_NAME, "Device not connected, use the connect() method."
260
+ )
261
+ raise
262
+
263
+ def read(self) -> bytes:
264
+ """ Reads from the device buffer.
265
+
266
+ Returns: Content of the device buffer.
267
+ """
268
+
269
+ n_total = 0
270
+ buf_size = 2048
271
+
272
+ # Set a timeout of READ_TIMEOUT to the socket.recv
273
+
274
+ saved_timeout = self.sock.gettimeout()
275
+ self.sock.settimeout(READ_TIMEOUT)
276
+
277
+ try:
278
+ for idx in range(100):
279
+ time.sleep(0.001) # Give the device time to fill the buffer
280
+ data = self.sock.recv(buf_size)
281
+ n = len(data)
282
+ n_total += n
283
+ if n < buf_size:
284
+ break
285
+ except socket.timeout:
286
+ logger.warning(f"Socket timeout error for {self.hostname}:{self.port}")
287
+ return b"\r\n"
288
+ except TimeoutError as exc:
289
+ logger.warning(f"Socket timeout error: {exc}")
290
+ return b"\r\n"
291
+ finally:
292
+ self.sock.settimeout(saved_timeout)
293
+
294
+ # logger.debug(f"Total number of bytes received is {n_total}, idx={idx}")
295
+
296
+ return data
297
+
298
+
299
+ if __name__ == "__main__":
300
+
301
+ daq = DAQ6510EthernetInterface()
302
+
303
+ with Timer():
304
+ daq.connect()
305
+
306
+ # print(daq.info())
307
+
308
+ # Initialize
309
+
310
+ daq.write('TRAC:DEL "test1"\n')
311
+
312
+ for cmd, response in [
313
+ ('TRAC:MAKE "test1", 1000', False), # create a new buffer
314
+ # settings for channel 1 and 2 of slot 1
315
+ ('SENS:FUNC "TEMP", (@101:102)', False), # set the function to temperature
316
+ ("SENS:TEMP:TRAN FRTD, (@101:102)", False), # set the transducer to 4-wire RTD
317
+ ("SENS:TEMP:RTD:FOUR PT100, (@101:102)", False), # set the type of the 4-wire RTD
318
+ ('ROUT:SCAN:BUFF "test1"', False),
319
+ ("ROUT:SCAN:CRE (@101:102)", False),
320
+ ("ROUT:CHAN:OPEN (@101:102)", False),
321
+ ("ROUT:STAT? (@101:102)", True),
322
+ ("ROUT:SCAN:STAR:STIM NONE", False),
323
+ # ("ROUT:SCAN:ADD:SING (@101, 102)", False), # not sure what this does, not really needed
324
+ ("ROUT:SCAN:COUN:SCAN 1", False), # not sure if this is needed in this setting
325
+ # ("ROUT:SCAN:INT 1", False),
326
+ ]:
327
+ print(f"Sending {cmd}...")
328
+ if response:
329
+ print(daq.trans(cmd), end="")
330
+ else:
331
+ daq.write(cmd)
332
+
333
+ # Read out the channels
334
+
335
+ # daq.write('TRAC:CLE "test1"\n')
336
+
337
+ for _ in range(10):
338
+ daq.write("INIT:IMM")
339
+ daq.write("*WAI")
340
+
341
+ # Reading the data
342
+
343
+ # When a trigger mode is running, these READ? commands can not be used.
344
+
345
+ # print(daq.trans('READ? "test1", CHAN, TST, READ\n', wait=False), end="")
346
+ # print(daq.trans('READ? "test1", CHAN, TST, READ\n', wait=False), end="")
347
+ # time.sleep(1)
348
+ # print(daq.trans('READ? "test1", CHAN, TST, READ\n', wait=False), end="")
349
+ # print(daq.trans('READ? "test1", CHAN, TST, READ\n', wait=False), end="")
350
+
351
+ # Read out the buffer
352
+
353
+ response = daq.trans('TRAC:DATA? 1, 2, "test1", CHAN, TST, READ')
354
+ ch1, tst1, val1, ch2, tst2, val2 = response[:-1].split(",")
355
+ print(f"Channel: {ch1} Time: {tst1} Value: {float(val1):.4f}\t", end="")
356
+ print(f"Channel: {ch2} Time: {tst2} Value: {float(val2):.4f}")
357
+ time.sleep(2)
358
+
359
+ daq.disconnect()