keithley-tempcontrol 0.17.3__tar.gz → 0.18.1__tar.gz
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.
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/.gitignore +6 -1
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/PKG-INFO +1 -1
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/pyproject.toml +3 -4
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/service_registry.db +0 -0
- keithley_tempcontrol-0.18.1/service_registry.db-shm +0 -0
- keithley_tempcontrol-0.18.1/service_registry.db-wal +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/__init__.py +0 -2
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/daq6510.py +101 -255
- keithley_tempcontrol-0.18.1/src/egse/tempcontrol/keithley/daq6510_adev.py +89 -0
- keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_mon.py → keithley_tempcontrol-0.18.1/src/egse/tempcontrol/keithley/daq6510_amon.py +70 -25
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/daq6510_cs.py +115 -44
- keithley_tempcontrol-0.18.1/src/egse/tempcontrol/keithley/daq6510_dev.py +154 -0
- keithley_tempcontrol-0.18.1/src/egse/tempcontrol/keithley/daq6510_mon.py +313 -0
- keithley_tempcontrol-0.18.1/src/egse/tempcontrol/keithley/daq6510_protocol.py +109 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/daq6510_sim.py +119 -62
- keithley_tempcontrol-0.18.1/src/keithley_tempcontrol/cgse_services.py +129 -0
- keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_adev.py +0 -97
- keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_dev.py +0 -287
- keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_protocol.py +0 -104
- keithley_tempcontrol-0.17.3/src/keithley_tempcontrol/cgse_services.py +0 -78
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/README.md +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/justfile +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/noxfile.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/daq6510.yaml +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/keithley_tempcontrol/__init__.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/keithley_tempcontrol/cgse_explore.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/keithley_tempcontrol/settings.yaml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "keithley-tempcontrol"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.1"
|
|
4
4
|
description = "Keithley Temperature Control for CGSE"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "IvS KU Leuven"}
|
|
@@ -32,6 +32,7 @@ test = ["pytest", "pytest-mock", "pytest-cov"]
|
|
|
32
32
|
[project.scripts]
|
|
33
33
|
daq6510_cs = 'egse.tempcontrol.keithley.daq6510_cs:app'
|
|
34
34
|
daq6510_sim = 'egse.tempcontrol.keithley.daq6510_sim:app'
|
|
35
|
+
daq6510_mon = 'egse.tempcontrol.keithley.daq6510_mon:app'
|
|
35
36
|
|
|
36
37
|
[project.gui-scripts]
|
|
37
38
|
daq6510_ui = "egse.tempcontrol.keithley.daq6510_ui:main"
|
|
@@ -73,12 +74,10 @@ build-backend = "hatchling.build"
|
|
|
73
74
|
|
|
74
75
|
[dependency-groups]
|
|
75
76
|
dev = [
|
|
76
|
-
"setuptools", # needed by PyCharm
|
|
77
77
|
"pytest>=8.3.4",
|
|
78
78
|
"pytest-cov>=6.0.0",
|
|
79
79
|
"pytest-mock>=3.14.0",
|
|
80
|
+
"pytest-asyncio>=0.26.0",
|
|
80
81
|
"ruff>=0.9.0",
|
|
81
82
|
"nox>=2025.2.9",
|
|
82
|
-
"setuptools>=75.8.2", # needed by PyCharm
|
|
83
|
-
"pytest-asyncio>=0.26.0",
|
|
84
83
|
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
{keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.18.1}/src/egse/tempcontrol/keithley/daq6510.py
RENAMED
|
@@ -1,33 +1,53 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import re
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import Dict,
|
|
5
|
-
from typing import List
|
|
6
|
-
from typing import Tuple
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
7
4
|
|
|
5
|
+
from egse.connect import get_endpoint
|
|
8
6
|
from egse.decorators import dynamic_interface
|
|
9
|
-
from egse.device import DeviceConnectionState
|
|
10
|
-
from egse.
|
|
11
|
-
from egse.
|
|
12
|
-
from egse.
|
|
13
|
-
from egse.mixin import dynamic_command
|
|
14
|
-
from egse.proxy import
|
|
7
|
+
from egse.device import DeviceConnectionState, DeviceInterface
|
|
8
|
+
from egse.env import bool_env
|
|
9
|
+
from egse.response import Failure
|
|
10
|
+
from egse.log import logger
|
|
11
|
+
from egse.mixin import CommandType, DynamicCommandMixin, add_lf, dynamic_command
|
|
12
|
+
from egse.proxy import DynamicProxy
|
|
13
|
+
from egse.scpi import count_number_of_channels, create_channel_list, get_channel_names
|
|
15
14
|
from egse.settings import Settings
|
|
16
|
-
from egse.tempcontrol.keithley.daq6510_dev import
|
|
17
|
-
from egse.zmq_ser import connect_address
|
|
15
|
+
from egse.tempcontrol.keithley.daq6510_dev import DAQ6510
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG")
|
|
20
18
|
|
|
21
19
|
HERE = Path(__file__).parent
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
DEVICE_SETTINGS = Settings.load(location=HERE, filename="daq6510.yaml")
|
|
21
|
+
cs_settings = Settings.load("Keithley Control Server")
|
|
22
|
+
dev_settings = Settings.load("Keithley DAQ6510")
|
|
26
23
|
|
|
24
|
+
PROTOCOL = cs_settings.get("PROTOCOL", "tcp")
|
|
25
|
+
HOSTNAME = cs_settings.get("HOSTNAME", "localhost")
|
|
26
|
+
COMMANDING_PORT = cs_settings.get("COMMANDING_PORT", 0)
|
|
27
|
+
TIMEOUT = cs_settings.get("TIMEOUT", 60)
|
|
28
|
+
SERVICE_TYPE = cs_settings.get("SERVICE_TYPE", "daq6510")
|
|
27
29
|
|
|
28
30
|
DEFAULT_BUFFER_1 = "defbuffer1"
|
|
29
31
|
DEFAULT_BUFFER_2 = "defbuffer2"
|
|
30
32
|
|
|
33
|
+
DEV_HOST = dev_settings.get("HOSTNAME", "localhost")
|
|
34
|
+
DEV_PORT = dev_settings.get("PORT", 5025)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def decode_response(response: bytes) -> str | Failure:
|
|
38
|
+
"""Decodes the bytes object, strips off the trailing 'CRLF'."""
|
|
39
|
+
|
|
40
|
+
if VERBOSE_DEBUG:
|
|
41
|
+
logger.debug(f"{response = } <- decode_response")
|
|
42
|
+
|
|
43
|
+
if isinstance(response, Failure):
|
|
44
|
+
return response
|
|
45
|
+
|
|
46
|
+
if isinstance(response, memoryview):
|
|
47
|
+
response = response.tobytes()
|
|
48
|
+
|
|
49
|
+
return response.decode().rstrip()
|
|
50
|
+
|
|
31
51
|
|
|
32
52
|
class DAQ6510Interface(DeviceInterface):
|
|
33
53
|
"""
|
|
@@ -35,7 +55,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
35
55
|
"""
|
|
36
56
|
|
|
37
57
|
@dynamic_interface
|
|
38
|
-
def send_command(self, command: str, response: bool) ->
|
|
58
|
+
def send_command(self, command: str, response: bool) -> str | None:
|
|
39
59
|
"""Sends the given SCPI command to the device.
|
|
40
60
|
|
|
41
61
|
The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
|
|
@@ -54,6 +74,23 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
54
74
|
cmd_type=CommandType.TRANSACTION,
|
|
55
75
|
cmd_string="*IDN?",
|
|
56
76
|
process_cmd_string=add_lf,
|
|
77
|
+
process_response=decode_response,
|
|
78
|
+
)
|
|
79
|
+
def get_idn(self) -> str:
|
|
80
|
+
"""Returns basic information about the device, its name, firmware version, etc.
|
|
81
|
+
|
|
82
|
+
The string returned is subject to change without notice and can not be used for parsing information.
|
|
83
|
+
|
|
84
|
+
Returns: Identification string of the instrument.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
raise NotImplementedError
|
|
88
|
+
|
|
89
|
+
@dynamic_command(
|
|
90
|
+
cmd_type=CommandType.TRANSACTION,
|
|
91
|
+
cmd_string="*IDN?",
|
|
92
|
+
process_cmd_string=add_lf,
|
|
93
|
+
process_response=decode_response,
|
|
57
94
|
)
|
|
58
95
|
def info(self) -> str:
|
|
59
96
|
"""Returns basic information about the device, its name, firmware version, etc.
|
|
@@ -98,7 +135,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
98
135
|
|
|
99
136
|
raise NotImplementedError
|
|
100
137
|
|
|
101
|
-
@dynamic_command(cmd_type=CommandType.TRANSACTION, cmd_string=":SYST:TIME? 1")
|
|
138
|
+
@dynamic_command(cmd_type=CommandType.TRANSACTION, cmd_string=":SYST:TIME? 1", process_response=decode_response)
|
|
102
139
|
def get_time(self) -> str:
|
|
103
140
|
"""Gets the date and time from the device in UTC.
|
|
104
141
|
|
|
@@ -126,7 +163,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
126
163
|
|
|
127
164
|
raise NotImplementedError
|
|
128
165
|
|
|
129
|
-
@dynamic_command(cmd_type=CommandType.TRANSACTION, cmd_string=
|
|
166
|
+
@dynamic_command(cmd_type=CommandType.TRANSACTION, cmd_string='TRAC:ACTUAL? "${buffer_name}"')
|
|
130
167
|
def get_buffer_count(self, buffer_name: str = DEFAULT_BUFFER_1):
|
|
131
168
|
"""Returns the number of data points in the specified buffer.
|
|
132
169
|
|
|
@@ -136,8 +173,12 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
136
173
|
|
|
137
174
|
raise NotImplementedError
|
|
138
175
|
|
|
139
|
-
@dynamic_command(
|
|
140
|
-
|
|
176
|
+
@dynamic_command(
|
|
177
|
+
cmd_type=CommandType.TRANSACTION,
|
|
178
|
+
cmd_string='TRACE:POINTS? "${buffer_name}"',
|
|
179
|
+
process_cmd_string=add_lf,
|
|
180
|
+
)
|
|
181
|
+
def get_buffer_capacity(self, buffer_name: str = DEFAULT_BUFFER_1):
|
|
141
182
|
"""Returns the capacity of the specified buffer.
|
|
142
183
|
|
|
143
184
|
Args:
|
|
@@ -146,7 +187,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
146
187
|
|
|
147
188
|
raise NotImplementedError
|
|
148
189
|
|
|
149
|
-
@dynamic_command(cmd_type=CommandType.WRITE, cmd_string=
|
|
190
|
+
@dynamic_command(cmd_type=CommandType.WRITE, cmd_string='TRACE:DELETE "${buffer_name}"')
|
|
150
191
|
def delete_buffer(self, buffer_name: str) -> None:
|
|
151
192
|
"""Deletes the specified buffer.
|
|
152
193
|
|
|
@@ -189,7 +230,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
189
230
|
raise NotImplementedError
|
|
190
231
|
|
|
191
232
|
@dynamic_interface
|
|
192
|
-
def setup_measurements(self, *, buffer_name: str, channel_list: str):
|
|
233
|
+
def setup_measurements(self, *, buffer_name: str = DEFAULT_BUFFER_1, channel_list: str):
|
|
193
234
|
"""Sets up the measurements for the given channel list.
|
|
194
235
|
|
|
195
236
|
Args:
|
|
@@ -200,11 +241,13 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
200
241
|
raise NotImplementedError
|
|
201
242
|
|
|
202
243
|
@dynamic_interface
|
|
203
|
-
def perform_measurement(
|
|
244
|
+
def perform_measurement(
|
|
245
|
+
self, *, buffer_name: str = DEFAULT_BUFFER_1, channel_list: str, count: int, interval: int
|
|
246
|
+
) -> list:
|
|
204
247
|
"""Performs the actual measurements.
|
|
205
248
|
|
|
206
249
|
Args:
|
|
207
|
-
buffer_name (str): Name of the buffer
|
|
250
|
+
buffer_name (str): Name of the buffer [default: defbuffer1]
|
|
208
251
|
channel_list (str): List of channels, as understood by the device
|
|
209
252
|
count (int): Number of measurements to perform
|
|
210
253
|
interval (int): Interval between measurements [s]
|
|
@@ -219,12 +262,12 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
219
262
|
through an Ethernet interface.
|
|
220
263
|
"""
|
|
221
264
|
|
|
222
|
-
def __init__(self, hostname: str =
|
|
265
|
+
def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT):
|
|
223
266
|
"""Opens a TCP/IP socket connection with the Keithley DAQ6510 Hardware.
|
|
224
267
|
|
|
225
268
|
Args:
|
|
226
|
-
hostname (str): IP address or fully qualified hostname of the
|
|
227
|
-
|
|
269
|
+
hostname (str): IP address or fully qualified hostname of the DAQ6510 hardware controller.
|
|
270
|
+
The default is defined in the ``settings.yaml`` configuration file.
|
|
228
271
|
port (int): IP port number to connect to, by default set in the ``settings.yaml`` configuration file.
|
|
229
272
|
|
|
230
273
|
Raises:
|
|
@@ -235,7 +278,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
235
278
|
|
|
236
279
|
logger.debug(f"Initializing the DAQ6510 Controller with hostname={hostname} on port={port}")
|
|
237
280
|
|
|
238
|
-
self.daq = self.transport =
|
|
281
|
+
self.daq = self.transport = DAQ6510(hostname, port)
|
|
239
282
|
|
|
240
283
|
# We set the default buffer here, this can be changed with the `create_buffer()` method.
|
|
241
284
|
|
|
@@ -277,7 +320,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
277
320
|
|
|
278
321
|
return self.daq.is_connected()
|
|
279
322
|
|
|
280
|
-
def send_command(self, command: str, response: bool) ->
|
|
323
|
+
def send_command(self, command: str, response: bool) -> str | None:
|
|
281
324
|
"""Sends an SCPI command to the device.
|
|
282
325
|
|
|
283
326
|
The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
|
|
@@ -290,9 +333,11 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
290
333
|
None will be returned.
|
|
291
334
|
"""
|
|
292
335
|
|
|
293
|
-
return self.daq.trans(command) if response else self.daq.write(command)
|
|
336
|
+
return self.daq.trans(command).decode() if response else self.daq.write(command)
|
|
294
337
|
|
|
295
|
-
def read_buffer(
|
|
338
|
+
def read_buffer(
|
|
339
|
+
self, start: int, end: int, buffer_name: str = DEFAULT_BUFFER_1, elements: list[str] | None = None
|
|
340
|
+
) -> bytes:
|
|
296
341
|
"""Reads specific data elements (measurements) from the given buffer.
|
|
297
342
|
|
|
298
343
|
Elements that can be specified to read out:
|
|
@@ -308,17 +353,17 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
308
353
|
start: (int) First index of the buffer that should be returned (>= 1)
|
|
309
354
|
end (int): Last index of the buffer that should be returned
|
|
310
355
|
buffer_name (str): Name of the buffer to read out
|
|
311
|
-
elements (
|
|
356
|
+
elements (list[str] | None): List of elements from the buffer to include in the response
|
|
312
357
|
|
|
313
358
|
Returns: List of all the readings.
|
|
314
359
|
"""
|
|
315
360
|
|
|
316
361
|
if elements is None:
|
|
317
|
-
|
|
362
|
+
elements_str = ["READING"]
|
|
318
363
|
else:
|
|
319
|
-
|
|
364
|
+
elements_str = ", ".join(elements)
|
|
320
365
|
|
|
321
|
-
return self.daq.trans(f'TRACE:DATA? {start}, {end}, "{buffer_name}", {
|
|
366
|
+
return self.daq.trans(f'TRACE:DATA? {start}, {end}, "{buffer_name}", {elements_str}')
|
|
322
367
|
|
|
323
368
|
def clear_buffer(self, buffer_name: str = DEFAULT_BUFFER_1) -> None:
|
|
324
369
|
"""Clears the given buffer.
|
|
@@ -473,7 +518,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
473
518
|
|
|
474
519
|
# Read out the buffer
|
|
475
520
|
|
|
476
|
-
logger.debug("Buffer count =
|
|
521
|
+
logger.debug(f"Buffer count = {self.get_buffer_count()}")
|
|
477
522
|
|
|
478
523
|
num_sensors = count_number_of_channels(channel_list)
|
|
479
524
|
|
|
@@ -483,82 +528,24 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
483
528
|
response = self.read_buffer(
|
|
484
529
|
idx, idx, buffer_name=buffer_name, elements=["CHANNEL", "TSTAMP", "READING", "UNIT"]
|
|
485
530
|
)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
531
|
+
response_str = response.decode().strip()
|
|
532
|
+
|
|
533
|
+
if response_str != "" and response_str != str(count * num_sensors):
|
|
534
|
+
if "\n" in response_str:
|
|
535
|
+
response_str = response_str.split("\n")
|
|
536
|
+
for i in range(len(response_str)):
|
|
537
|
+
readings.append(response_str[i].split(","))
|
|
491
538
|
else:
|
|
492
|
-
readings.append(
|
|
539
|
+
readings.append(response_str.split(","))
|
|
540
|
+
|
|
541
|
+
# Remove incomplete readings
|
|
493
542
|
if len(readings[0]) < 4:
|
|
494
543
|
del readings[0]
|
|
495
544
|
|
|
496
545
|
return readings
|
|
497
546
|
|
|
498
547
|
|
|
499
|
-
class
|
|
500
|
-
"""
|
|
501
|
-
Simulator for the Keithley DAQ6510 system.
|
|
502
|
-
"""
|
|
503
|
-
|
|
504
|
-
def read_buffer(self, start: int, end: int, buffer_name: str, elements: List[str]):
|
|
505
|
-
pass
|
|
506
|
-
|
|
507
|
-
def get_buffer_count(self, buffer_name: str = DEFAULT_BUFFER_1):
|
|
508
|
-
pass
|
|
509
|
-
|
|
510
|
-
def get_buffer_capacity(self, buffer_name: str):
|
|
511
|
-
pass
|
|
512
|
-
|
|
513
|
-
def delete_buffer(self, buffer_name: str):
|
|
514
|
-
pass
|
|
515
|
-
|
|
516
|
-
def clear_buffer(self, buffer_name: str):
|
|
517
|
-
pass
|
|
518
|
-
|
|
519
|
-
def create_buffer(self, buffer_name: str, size: int):
|
|
520
|
-
pass
|
|
521
|
-
|
|
522
|
-
def configure_sensors(self, channel_list: str, *, sense: Dict[str, List[Tuple]]):
|
|
523
|
-
pass
|
|
524
|
-
|
|
525
|
-
def setup_measurements(self, *, buffer_name: str, channel_list: str):
|
|
526
|
-
pass
|
|
527
|
-
|
|
528
|
-
def perform_measurement(self, *, buffer_name: str, channel_list: str, count: int, interval: int):
|
|
529
|
-
pass
|
|
530
|
-
|
|
531
|
-
def send_command(self, command: str, response: bool):
|
|
532
|
-
pass
|
|
533
|
-
|
|
534
|
-
def info(self) -> str:
|
|
535
|
-
pass
|
|
536
|
-
|
|
537
|
-
def reset(self):
|
|
538
|
-
pass
|
|
539
|
-
|
|
540
|
-
def is_simulator(self):
|
|
541
|
-
"""Indicates that the device is a simulator.
|
|
542
|
-
|
|
543
|
-
Returns: True.
|
|
544
|
-
"""
|
|
545
|
-
|
|
546
|
-
return True
|
|
547
|
-
|
|
548
|
-
def connect(self):
|
|
549
|
-
pass
|
|
550
|
-
|
|
551
|
-
def disconnect(self):
|
|
552
|
-
pass
|
|
553
|
-
|
|
554
|
-
def reconnect(self):
|
|
555
|
-
pass
|
|
556
|
-
|
|
557
|
-
def is_connected(self):
|
|
558
|
-
pass
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
class DAQ6510Proxy(Proxy, DAQ6510Interface):
|
|
548
|
+
class DAQ6510Proxy(DynamicProxy, DAQ6510Interface):
|
|
562
549
|
"""
|
|
563
550
|
The DAQ6510Proxy class is used to connect to the Keithley Control Server and send commands
|
|
564
551
|
to the Keithley Hardware Controller remotely.
|
|
@@ -566,162 +553,21 @@ class DAQ6510Proxy(Proxy, DAQ6510Interface):
|
|
|
566
553
|
|
|
567
554
|
def __init__(
|
|
568
555
|
self,
|
|
569
|
-
protocol: str =
|
|
570
|
-
hostname: str =
|
|
571
|
-
port: int =
|
|
572
|
-
timeout:
|
|
556
|
+
protocol: str = PROTOCOL,
|
|
557
|
+
hostname: str = HOSTNAME,
|
|
558
|
+
port: int = COMMANDING_PORT,
|
|
559
|
+
timeout: float = TIMEOUT, # Timeout [s]: > scan count * interval + (one scan duration)
|
|
573
560
|
):
|
|
574
|
-
"""
|
|
561
|
+
"""Initialization of a DAQ6510Proxy.
|
|
575
562
|
|
|
576
563
|
Args:
|
|
577
564
|
protocol (str): Transport protocol [default is taken from settings file]
|
|
578
565
|
hostname (str): Location of the Control Server (IP address) [default is taken from settings file]
|
|
579
566
|
port (int): TCP port on which the Control Server is listening for commands [default is taken from settings
|
|
580
567
|
file]
|
|
581
|
-
timeout (
|
|
568
|
+
timeout (float): Timeout by which to establish the connection [s]
|
|
582
569
|
"""
|
|
583
570
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def create_channel_list(*args) -> str:
|
|
588
|
-
"""Createa a channel list that is understood by the SCPI commands of the DAQ6510.
|
|
589
|
-
|
|
590
|
-
Channel names contain both the slot number and the channel number. The slot number is the number of the slot where
|
|
591
|
-
the card is installed at the back of the device.
|
|
592
|
-
|
|
593
|
-
When addressing multiple individual channels, add each of them as a separate argument, e.g. to include channels 1,
|
|
594
|
-
3, and 7 from slot 1, use the following command:
|
|
595
|
-
|
|
596
|
-
>>> create_channel_list(101, 103, 107)
|
|
597
|
-
'(@101, 103, 107)'
|
|
598
|
-
|
|
599
|
-
To designate a range of channels, only one argument should be given, i.e. a tuple containing two channels
|
|
600
|
-
representing the range. The following tuple `(101, 110)` will create the following response: `"(@101:110)"`. The
|
|
601
|
-
range is inclusive, so this will define a range of 10 channels in slot 1.
|
|
602
|
-
|
|
603
|
-
>>> create_channel_list((201, 205))
|
|
604
|
-
'(@201:205)'
|
|
605
|
-
|
|
606
|
-
See reference manual for the Keithley DAQ6510 [DAQ6510-901-01 Rev. B / September 2019], chapter 11: Introduction to
|
|
607
|
-
SCPI commands, SCPI command formatting, channel naming.
|
|
608
|
-
|
|
609
|
-
Args:
|
|
610
|
-
*args: Tuple or a list of channels
|
|
611
|
-
|
|
612
|
-
Returns: String containing the channel list as understood by the device.
|
|
613
|
-
"""
|
|
614
|
-
|
|
615
|
-
if not args:
|
|
616
|
-
return ""
|
|
617
|
-
|
|
618
|
-
# If only one argument is given, I expect either a tuple defining a range or just one channel. When several
|
|
619
|
-
# arguments are given, I expect them all to be individual channels.
|
|
620
|
-
|
|
621
|
-
if len(args) == 1:
|
|
622
|
-
arg = args[0]
|
|
623
|
-
if isinstance(arg, tuple):
|
|
624
|
-
ch_list = f"(@{arg[0]}:{arg[1]})"
|
|
625
|
-
else:
|
|
626
|
-
ch_list = f"(@{arg})"
|
|
627
|
-
|
|
628
|
-
else:
|
|
629
|
-
ch_list = "(@" + ", ".join([str(arg) for arg in args]) + ")"
|
|
630
|
-
|
|
631
|
-
return ch_list
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
def count_number_of_channels(channel_list: str) -> int:
|
|
635
|
-
"""Given a proper channel list, this function counts the number of channels.
|
|
636
|
-
|
|
637
|
-
For ranges, it returns the actual number of channels that are included in the range.
|
|
638
|
-
|
|
639
|
-
>>> count_number_of_channels("(@1,2,3,4,5)")
|
|
640
|
-
5
|
|
641
|
-
>>> count_number_of_channels("(@1, 3, 5)")
|
|
642
|
-
3
|
|
643
|
-
>>> count_number_of_channels("(@2:7)")
|
|
644
|
-
6
|
|
645
|
-
|
|
646
|
-
Args:
|
|
647
|
-
channel_list (str): Channel list as understood by the SCPI commands of DAQ6510
|
|
648
|
-
|
|
649
|
-
Returns: Number of channels in the list.
|
|
650
|
-
"""
|
|
651
|
-
|
|
652
|
-
match = re.match(r"\(@(.*)\)", channel_list)
|
|
653
|
-
group = match.groups()[0]
|
|
654
|
-
|
|
655
|
-
parts = group.replace(" ", "").split(",")
|
|
656
|
-
count = 0
|
|
657
|
-
for part in parts:
|
|
658
|
-
if ":" in part:
|
|
659
|
-
split_part = part.split(":")
|
|
660
|
-
count += int(split_part[1]) - int(split_part[0]) + 1
|
|
661
|
-
else:
|
|
662
|
-
count += 1
|
|
663
|
-
|
|
664
|
-
return count
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
def get_channel_names(channel_list: str) -> List[str]:
|
|
668
|
-
"""Generates a list of channel names from a given channel list.
|
|
669
|
-
|
|
670
|
-
Args:
|
|
671
|
-
channel_list (str): Channel list as understood by the SCPI commands of DAQ6510
|
|
672
|
-
|
|
673
|
-
Returns: List of channel names.
|
|
674
|
-
"""
|
|
675
|
-
|
|
676
|
-
match = re.match(r"\(@(.*)\)", channel_list)
|
|
677
|
-
group = match.groups()[0]
|
|
678
|
-
|
|
679
|
-
parts = group.replace(" ", "").split(",")
|
|
680
|
-
names = []
|
|
681
|
-
for part in parts:
|
|
682
|
-
if ":" in part:
|
|
683
|
-
split_part = part.split(":")
|
|
684
|
-
names.extend(str(ch) for ch in range(int(split_part[0]), int(split_part[1]) + 1))
|
|
685
|
-
else:
|
|
686
|
-
names.append(part)
|
|
687
|
-
|
|
688
|
-
return names
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if __name__ == "__main__":
|
|
692
|
-
logging.basicConfig(level=20)
|
|
693
|
-
|
|
694
|
-
print(f'{get_channel_names("(@101:105)")=}')
|
|
695
|
-
print(f'{get_channel_names("(@101, 102, 103, 105)")=}')
|
|
696
|
-
# sys.exit(0)
|
|
697
|
-
|
|
698
|
-
daq = DAQ6510Controller()
|
|
699
|
-
daq.connect()
|
|
700
|
-
daq.reset()
|
|
701
|
-
|
|
702
|
-
print(daq.info())
|
|
703
|
-
|
|
704
|
-
buffer_capacity = daq.get_buffer_capacity()
|
|
705
|
-
print(f"buffer {DEFAULT_BUFFER_1} can still hold {buffer_capacity} readings")
|
|
706
|
-
|
|
707
|
-
buffer_count = daq.get_buffer_count()
|
|
708
|
-
print(f"buffer {DEFAULT_BUFFER_1} holds {buffer_count} readings")
|
|
709
|
-
|
|
710
|
-
channels = create_channel_list((101, 102))
|
|
711
|
-
|
|
712
|
-
print(channels)
|
|
713
|
-
|
|
714
|
-
sense_dict = {"TEMPERATURE": [("TRANSDUCER", "FRTD"), ("RTD:FOUR", "PT100"), ("UNIT", "KELVIN")]}
|
|
715
|
-
|
|
716
|
-
daq.configure_sensors(channels, sense=sense_dict)
|
|
717
|
-
|
|
718
|
-
daq.setup_measurements(channel_list=channels)
|
|
719
|
-
|
|
720
|
-
meas_response = daq.perform_measurement(channel_list=channels, count=5, interval=1)
|
|
721
|
-
|
|
722
|
-
print(meas_response)
|
|
723
|
-
|
|
724
|
-
buffer_count = daq.get_buffer_count()
|
|
725
|
-
print(f"buffer {DEFAULT_BUFFER_1} holds {buffer_count} readings")
|
|
571
|
+
endpoint = get_endpoint(SERVICE_TYPE, protocol, hostname, port)
|
|
726
572
|
|
|
727
|
-
|
|
573
|
+
super().__init__(endpoint, timeout=timeout)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Keithley DAQ6510 device implementation as an SCPI interface.
|
|
3
|
+
|
|
4
|
+
This module provides an asynchronous interface to communicate with
|
|
5
|
+
the Keithley DAQ6510 data acquisition unit using SCPI commands.
|
|
6
|
+
|
|
7
|
+
Classes:
|
|
8
|
+
DAQ6510: An asynchronous SCPI interface for the Keithley DAQ6510 device.
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"DAQ6510",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
from typing import Any
|
|
18
|
+
from typing import Dict
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from egse.log import logger
|
|
22
|
+
from egse.scpi import AsyncSCPIInterface
|
|
23
|
+
from egse.settings import Settings
|
|
24
|
+
|
|
25
|
+
dev_settings = Settings.load("Keithley DAQ6510")
|
|
26
|
+
|
|
27
|
+
DEV_HOST = dev_settings.get("HOSTNAME", "localhost")
|
|
28
|
+
DEV_PORT = dev_settings.get("PORT", 5025)
|
|
29
|
+
DEVICE_NAME = dev_settings.get("DEVICE_NAME", "DAQ6510")
|
|
30
|
+
DEV_ID_VALIDATION = "DAQ6510"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DAQ6510(AsyncSCPIInterface):
|
|
34
|
+
"""Keithley DAQ6510 specific implementation."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT, settings: Optional[Dict[str, Any]] = None):
|
|
37
|
+
"""Initialize a Keithley DAQ6510 interface.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
hostname: Hostname or IP address
|
|
41
|
+
port: TCP port (default 5025 for SCPI)
|
|
42
|
+
settings: Additional device settings
|
|
43
|
+
"""
|
|
44
|
+
super().__init__(
|
|
45
|
+
device_name=DEVICE_NAME,
|
|
46
|
+
hostname=hostname,
|
|
47
|
+
port=port,
|
|
48
|
+
settings=settings,
|
|
49
|
+
id_validation=DEV_ID_VALIDATION, # String that must appear in IDN? response
|
|
50
|
+
read_timeout=5.0,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self._measurement_lock = asyncio.Lock()
|
|
54
|
+
|
|
55
|
+
async def get_measurement(self, channel: str) -> float:
|
|
56
|
+
"""Get a measurement from a specific channel.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
channel: Channel to measure (e.g., "101")
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The measured value as a float
|
|
63
|
+
"""
|
|
64
|
+
async with self._measurement_lock:
|
|
65
|
+
cmd = "INIT:IMM"
|
|
66
|
+
logger.info(f"Sending {cmd}...")
|
|
67
|
+
await self.write(cmd)
|
|
68
|
+
cmd = "*WAI"
|
|
69
|
+
logger.info(f"Sending {cmd}...")
|
|
70
|
+
await self.write(cmd)
|
|
71
|
+
|
|
72
|
+
if channel == "101":
|
|
73
|
+
start_index = end_index = 1
|
|
74
|
+
elif channel == "102":
|
|
75
|
+
start_index = end_index = 2
|
|
76
|
+
else:
|
|
77
|
+
return float("nan")
|
|
78
|
+
|
|
79
|
+
response = (
|
|
80
|
+
(await self.trans(f'TRAC:DATA? {start_index}, {end_index}, "test1", CHAN, TST, READ')).decode().strip()
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
logger.info(f"{response = }")
|
|
84
|
+
|
|
85
|
+
ch, tst, val = response.split(",")
|
|
86
|
+
|
|
87
|
+
logger.info(f"Channel: {ch} Time: {tst} Value: {float(val):.4f}")
|
|
88
|
+
|
|
89
|
+
return float(val)
|