keithley-tempcontrol 0.17.3__tar.gz → 0.17.4__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.17.4}/PKG-INFO +1 -1
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/pyproject.toml +1 -1
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510.py +31 -22
- keithley_tempcontrol-0.17.4/src/egse/tempcontrol/keithley/daq6510_adev.py +77 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510_cs.py +89 -44
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510_dev.py +78 -20
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510_mon.py +58 -22
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510_protocol.py +18 -35
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510_sim.py +117 -60
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/keithley_tempcontrol/cgse_services.py +26 -7
- keithley_tempcontrol-0.17.3/justfile +0 -25
- keithley_tempcontrol-0.17.3/service_registry.db +0 -0
- keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_adev.py +0 -97
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/.gitignore +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/README.md +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/noxfile.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/__init__.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510.yaml +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/keithley_tempcontrol/__init__.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/keithley_tempcontrol/cgse_explore.py +0 -0
- {keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/keithley_tempcontrol/settings.yaml +0 -0
{keithley_tempcontrol-0.17.3 → keithley_tempcontrol-0.17.4}/src/egse/tempcontrol/keithley/daq6510.py
RENAMED
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import re
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Dict
|
|
4
|
+
from typing import Dict
|
|
5
5
|
from typing import List
|
|
6
6
|
from typing import Tuple
|
|
7
7
|
|
|
8
|
+
from egse.connect import get_endpoint
|
|
8
9
|
from egse.decorators import dynamic_interface
|
|
9
10
|
from egse.device import DeviceConnectionState
|
|
10
11
|
from egse.device import DeviceInterface
|
|
11
|
-
from egse.
|
|
12
|
+
from egse.log import logger
|
|
13
|
+
from egse.mixin import CommandType
|
|
14
|
+
from egse.mixin import DynamicCommandMixin
|
|
12
15
|
from egse.mixin import add_lf
|
|
13
16
|
from egse.mixin import dynamic_command
|
|
14
17
|
from egse.proxy import Proxy
|
|
15
18
|
from egse.settings import Settings
|
|
16
|
-
from egse.tempcontrol.keithley.daq6510_dev import
|
|
17
|
-
from egse.zmq_ser import connect_address
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger(__name__)
|
|
19
|
+
from egse.tempcontrol.keithley.daq6510_dev import DAQ6510
|
|
20
20
|
|
|
21
21
|
HERE = Path(__file__).parent
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
DEVICE_SETTINGS = Settings.load(location=HERE, filename="daq6510.yaml")
|
|
23
|
+
cs_settings = Settings.load("Keithley Control Server")
|
|
24
|
+
dev_settings = Settings.load("Keithley DAQ6510")
|
|
26
25
|
|
|
26
|
+
PROTOCOL = cs_settings.get("PROTOCOL", "tcp")
|
|
27
|
+
HOSTNAME = cs_settings.get("HOSTNAME", "localhost")
|
|
28
|
+
COMMANDING_PORT = cs_settings.get("COMMANDING_PORT", 0)
|
|
29
|
+
TIMEOUT = cs_settings.get("TIMEOUT")
|
|
30
|
+
SERVICE_TYPE = cs_settings.get("SERVICE_TYPE", "daq6510")
|
|
27
31
|
|
|
28
32
|
DEFAULT_BUFFER_1 = "defbuffer1"
|
|
29
33
|
DEFAULT_BUFFER_2 = "defbuffer2"
|
|
30
34
|
|
|
35
|
+
DEV_HOST = dev_settings.get("HOSTNAME")
|
|
36
|
+
DEV_PORT = dev_settings.get("PORT")
|
|
37
|
+
|
|
31
38
|
|
|
32
39
|
class DAQ6510Interface(DeviceInterface):
|
|
33
40
|
"""
|
|
@@ -35,7 +42,7 @@ class DAQ6510Interface(DeviceInterface):
|
|
|
35
42
|
"""
|
|
36
43
|
|
|
37
44
|
@dynamic_interface
|
|
38
|
-
def send_command(self, command: str, response: bool) ->
|
|
45
|
+
def send_command(self, command: str, response: bool) -> str | None:
|
|
39
46
|
"""Sends the given SCPI command to the device.
|
|
40
47
|
|
|
41
48
|
The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
|
|
@@ -219,12 +226,12 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
219
226
|
through an Ethernet interface.
|
|
220
227
|
"""
|
|
221
228
|
|
|
222
|
-
def __init__(self, hostname: str =
|
|
229
|
+
def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT):
|
|
223
230
|
"""Opens a TCP/IP socket connection with the Keithley DAQ6510 Hardware.
|
|
224
231
|
|
|
225
232
|
Args:
|
|
226
|
-
hostname (str): IP address or fully qualified hostname of the
|
|
227
|
-
|
|
233
|
+
hostname (str): IP address or fully qualified hostname of the DAQ6510 hardware controller.
|
|
234
|
+
The default is defined in the ``settings.yaml`` configuration file.
|
|
228
235
|
port (int): IP port number to connect to, by default set in the ``settings.yaml`` configuration file.
|
|
229
236
|
|
|
230
237
|
Raises:
|
|
@@ -235,7 +242,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
235
242
|
|
|
236
243
|
logger.debug(f"Initializing the DAQ6510 Controller with hostname={hostname} on port={port}")
|
|
237
244
|
|
|
238
|
-
self.daq = self.transport =
|
|
245
|
+
self.daq = self.transport = DAQ6510(hostname, port)
|
|
239
246
|
|
|
240
247
|
# We set the default buffer here, this can be changed with the `create_buffer()` method.
|
|
241
248
|
|
|
@@ -277,7 +284,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
277
284
|
|
|
278
285
|
return self.daq.is_connected()
|
|
279
286
|
|
|
280
|
-
def send_command(self, command: str, response: bool) ->
|
|
287
|
+
def send_command(self, command: str, response: bool) -> str | None:
|
|
281
288
|
"""Sends an SCPI command to the device.
|
|
282
289
|
|
|
283
290
|
The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
|
|
@@ -292,7 +299,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
|
|
|
292
299
|
|
|
293
300
|
return self.daq.trans(command) if response else self.daq.write(command)
|
|
294
301
|
|
|
295
|
-
def read_buffer(self, start: int, end: int, buffer_name: str = DEFAULT_BUFFER_1, elements:
|
|
302
|
+
def read_buffer(self, start: int, end: int, buffer_name: str = DEFAULT_BUFFER_1, elements: list[str] = None):
|
|
296
303
|
"""Reads specific data elements (measurements) from the given buffer.
|
|
297
304
|
|
|
298
305
|
Elements that can be specified to read out:
|
|
@@ -566,10 +573,10 @@ class DAQ6510Proxy(Proxy, DAQ6510Interface):
|
|
|
566
573
|
|
|
567
574
|
def __init__(
|
|
568
575
|
self,
|
|
569
|
-
protocol: str =
|
|
570
|
-
hostname: str =
|
|
571
|
-
port: int =
|
|
572
|
-
timeout:
|
|
576
|
+
protocol: str = PROTOCOL,
|
|
577
|
+
hostname: str = HOSTNAME,
|
|
578
|
+
port: int = COMMANDING_PORT,
|
|
579
|
+
timeout: float = TIMEOUT, # Timeout [s]: > scan count * interval + (one scan duration)
|
|
573
580
|
):
|
|
574
581
|
"""Initialisation of a DAQ6510Proxy.
|
|
575
582
|
|
|
@@ -578,10 +585,12 @@ class DAQ6510Proxy(Proxy, DAQ6510Interface):
|
|
|
578
585
|
hostname (str): Location of the Control Server (IP address) [default is taken from settings file]
|
|
579
586
|
port (int): TCP port on which the Control Server is listening for commands [default is taken from settings
|
|
580
587
|
file]
|
|
581
|
-
timeout (
|
|
588
|
+
timeout (float): Timeout by which to establish the connection [s]
|
|
582
589
|
"""
|
|
583
590
|
|
|
584
|
-
|
|
591
|
+
endpoint = get_endpoint(SERVICE_TYPE, protocol, hostname, port)
|
|
592
|
+
|
|
593
|
+
super().__init__(endpoint, timeout=timeout)
|
|
585
594
|
|
|
586
595
|
|
|
587
596
|
def create_channel_list(*args) -> str:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DAQ6510",
|
|
3
|
+
]
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import Dict
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from egse.log import logger
|
|
11
|
+
from egse.scpi import AsyncSCPIInterface
|
|
12
|
+
from egse.settings import Settings
|
|
13
|
+
|
|
14
|
+
dev_settings = Settings.load("Keithley DAQ6510")
|
|
15
|
+
|
|
16
|
+
DEV_HOST = dev_settings.get("HOSTNAME", "localhost")
|
|
17
|
+
DEV_PORT = dev_settings.get("PORT", 0)
|
|
18
|
+
DEVICE_NAME = dev_settings.get("DEVICE_NAME", "DAQ6510")
|
|
19
|
+
DEV_ID_VALIDATION = "DAQ6510"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DAQ6510(AsyncSCPIInterface):
|
|
23
|
+
"""Keithley DAQ6510 specific implementation."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT, settings: Optional[Dict[str, Any]] = None):
|
|
26
|
+
"""Initialize a Keithley DAQ6510 interface.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
hostname: Hostname or IP address
|
|
30
|
+
port: TCP port (default 5025 for SCPI)
|
|
31
|
+
settings: Additional device settings
|
|
32
|
+
"""
|
|
33
|
+
super().__init__(
|
|
34
|
+
device_name=DEVICE_NAME,
|
|
35
|
+
hostname=hostname,
|
|
36
|
+
port=port,
|
|
37
|
+
settings=settings,
|
|
38
|
+
id_validation=DEV_ID_VALIDATION, # String that must appear in IDN? response
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self._measurement_lock = asyncio.Lock()
|
|
42
|
+
|
|
43
|
+
async def get_measurement(self, channel: str) -> float:
|
|
44
|
+
"""Get a measurement from a specific channel.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
channel: Channel to measure (e.g., "101")
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
The measured value as a float
|
|
51
|
+
"""
|
|
52
|
+
async with self._measurement_lock:
|
|
53
|
+
cmd = "INIT:IMM"
|
|
54
|
+
logger.info(f"Sending {cmd}...")
|
|
55
|
+
await self.write(cmd)
|
|
56
|
+
cmd = "*WAI"
|
|
57
|
+
logger.info(f"Sending {cmd}...")
|
|
58
|
+
await self.write(cmd)
|
|
59
|
+
|
|
60
|
+
if channel == "101":
|
|
61
|
+
start_index = end_index = 1
|
|
62
|
+
elif channel == "102":
|
|
63
|
+
start_index = end_index = 2
|
|
64
|
+
else:
|
|
65
|
+
return float("nan")
|
|
66
|
+
|
|
67
|
+
response = (
|
|
68
|
+
(await self.trans(f'TRAC:DATA? {start_index}, {end_index}, "test1", CHAN, TST, READ')).decode().strip()
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
logger.info(f"{response = }")
|
|
72
|
+
|
|
73
|
+
ch, tst, val = response.split(",")
|
|
74
|
+
|
|
75
|
+
logger.info(f"Channel: {ch} Time: {tst} Value: {float(val):.4f}")
|
|
76
|
+
|
|
77
|
+
return float(val)
|
|
@@ -1,22 +1,29 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import multiprocessing
|
|
3
2
|
import sys
|
|
4
3
|
|
|
5
|
-
import click
|
|
6
|
-
import invoke
|
|
7
4
|
import rich
|
|
5
|
+
import typer
|
|
8
6
|
import zmq
|
|
7
|
+
|
|
8
|
+
from egse.connect import get_endpoint
|
|
9
9
|
from egse.control import ControlServer
|
|
10
10
|
from egse.control import is_control_server_active
|
|
11
|
+
from egse.log import logger
|
|
12
|
+
from egse.logger import remote_logging
|
|
13
|
+
from egse.registry.client import RegistryClient
|
|
11
14
|
from egse.settings import Settings
|
|
15
|
+
from egse.storage import store_housekeeping_information
|
|
12
16
|
from egse.tempcontrol.keithley.daq6510 import DAQ6510Proxy
|
|
13
17
|
from egse.tempcontrol.keithley.daq6510_protocol import DAQ6510Protocol
|
|
14
18
|
from egse.zmq_ser import connect_address
|
|
15
|
-
from prometheus_client import start_http_server
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
cs_settings = Settings.load("Keithley Control Server")
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
PROTOCOL = cs_settings.get("PROTOCOL", "tcp")
|
|
23
|
+
HOSTNAME = cs_settings.get("HOSTNAME", "localhost")
|
|
24
|
+
COMMANDING_PORT = cs_settings.get("COMMANDING_PORT", 0)
|
|
25
|
+
STORAGE_MNEMONIC = cs_settings.get("STORAGE_MNEMONIC", "DAQ6510")
|
|
26
|
+
SERVICE_TYPE = cs_settings.get("SERVICE_TYPE", "daq6510")
|
|
20
27
|
|
|
21
28
|
|
|
22
29
|
def is_daq6510_cs_active(timeout: float = 0.5) -> bool:
|
|
@@ -28,7 +35,14 @@ def is_daq6510_cs_active(timeout: float = 0.5) -> bool:
|
|
|
28
35
|
Returns: True if the Control Server is running and replied with the expected answer; False otherwise.
|
|
29
36
|
"""
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
if COMMANDING_PORT == 0:
|
|
39
|
+
with RegistryClient() as client:
|
|
40
|
+
endpoint = client.get_endpoint(SERVICE_TYPE)
|
|
41
|
+
if endpoint is None:
|
|
42
|
+
logger.debug(f"No endpoint for {SERVICE_TYPE}")
|
|
43
|
+
return False
|
|
44
|
+
else:
|
|
45
|
+
endpoint = connect_address(PROTOCOL, HOSTNAME, COMMANDING_PORT)
|
|
32
46
|
|
|
33
47
|
return is_control_server_active(endpoint, timeout)
|
|
34
48
|
|
|
@@ -62,13 +76,15 @@ class DAQ6510ControlServer(ControlServer):
|
|
|
62
76
|
|
|
63
77
|
self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)
|
|
64
78
|
|
|
79
|
+
self.register_service(service_type=cs_settings.SERVICE_TYPE)
|
|
80
|
+
|
|
65
81
|
def get_communication_protocol(self) -> str:
|
|
66
82
|
"""Returns the communication protocol used by the Control Server.
|
|
67
83
|
|
|
68
84
|
Returns: Communication protocol used by the Control Server, as specified in the settings.
|
|
69
85
|
"""
|
|
70
86
|
|
|
71
|
-
return
|
|
87
|
+
return cs_settings.PROTOCOL
|
|
72
88
|
|
|
73
89
|
def get_commanding_port(self) -> int:
|
|
74
90
|
"""Returns the commanding port used by the Control Server.
|
|
@@ -76,7 +92,7 @@ class DAQ6510ControlServer(ControlServer):
|
|
|
76
92
|
Returns: Commanding port used by the Control Server, as specified in the settings.
|
|
77
93
|
"""
|
|
78
94
|
|
|
79
|
-
return
|
|
95
|
+
return cs_settings.COMMANDING_PORT
|
|
80
96
|
|
|
81
97
|
def get_service_port(self):
|
|
82
98
|
"""Returns the service port used by the Control Server.
|
|
@@ -84,7 +100,7 @@ class DAQ6510ControlServer(ControlServer):
|
|
|
84
100
|
Returns: Service port used by the Control Server, as specified in the settings.
|
|
85
101
|
"""
|
|
86
102
|
|
|
87
|
-
return
|
|
103
|
+
return cs_settings.SERVICE_PORT
|
|
88
104
|
|
|
89
105
|
def get_monitoring_port(self):
|
|
90
106
|
"""Returns the monitoring port used by the Control Server.
|
|
@@ -92,7 +108,7 @@ class DAQ6510ControlServer(ControlServer):
|
|
|
92
108
|
Returns: Monitoring port used by the Control Server, as specified in the settings.
|
|
93
109
|
"""
|
|
94
110
|
|
|
95
|
-
return
|
|
111
|
+
return cs_settings.MONITORING_PORT
|
|
96
112
|
|
|
97
113
|
def get_storage_mnemonic(self):
|
|
98
114
|
"""Returns the storage mnemonics used by the Control Server.
|
|
@@ -104,57 +120,88 @@ class DAQ6510ControlServer(ControlServer):
|
|
|
104
120
|
settings, "DAQ6510" will be used.
|
|
105
121
|
"""
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
return STORAGE_MNEMONIC
|
|
124
|
+
|
|
125
|
+
def is_storage_manager_active(self):
|
|
126
|
+
from egse.storage import is_storage_manager_active
|
|
127
|
+
|
|
128
|
+
return is_storage_manager_active()
|
|
129
|
+
|
|
130
|
+
def store_housekeeping_information(self, data):
|
|
131
|
+
"""Send housekeeping information to the Storage manager."""
|
|
132
|
+
|
|
133
|
+
origin = self.get_storage_mnemonic()
|
|
134
|
+
store_housekeeping_information(origin, data)
|
|
135
|
+
|
|
136
|
+
def register_to_storage_manager(self):
|
|
137
|
+
from egse.storage import register_to_storage_manager
|
|
138
|
+
from egse.storage.persistence import TYPES
|
|
139
|
+
|
|
140
|
+
register_to_storage_manager(
|
|
141
|
+
origin=self.get_storage_mnemonic(),
|
|
142
|
+
persistence_class=TYPES["CSV"],
|
|
143
|
+
prep={
|
|
144
|
+
"column_names": list(self.device_protocol.get_housekeeping().keys()),
|
|
145
|
+
"mode": "a",
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def unregister_from_storage_manager(self):
|
|
150
|
+
from egse.storage import unregister_from_storage_manager
|
|
151
|
+
|
|
152
|
+
unregister_from_storage_manager(origin=self.get_storage_mnemonic())
|
|
111
153
|
|
|
112
154
|
def before_serve(self):
|
|
113
155
|
"""Steps to take before the Control Server is activated."""
|
|
114
156
|
|
|
115
|
-
start_http_server(CTRL_SETTINGS.METRICS_PORT)
|
|
116
|
-
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
def cli():
|
|
120
|
-
pass
|
|
158
|
+
app = typer.Typer(name="daq6510_cs")
|
|
121
159
|
|
|
122
160
|
|
|
123
|
-
@
|
|
161
|
+
@app.command()
|
|
124
162
|
def start():
|
|
125
163
|
"""Starts the Keithley DAQ6510 Control Server."""
|
|
126
164
|
|
|
127
165
|
multiprocessing.current_process().name = "daq6510_cs (start)"
|
|
128
166
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
167
|
+
with remote_logging():
|
|
168
|
+
from egse.env import setup_env
|
|
169
|
+
|
|
170
|
+
setup_env()
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
control_server = DAQ6510ControlServer()
|
|
174
|
+
control_server.serve()
|
|
175
|
+
except KeyboardInterrupt:
|
|
176
|
+
logger.debug("Shutdown requested...exiting")
|
|
177
|
+
except SystemExit as exit_code:
|
|
178
|
+
logger.debug("System Exit with code {}.".format(exit_code))
|
|
179
|
+
sys.exit(exit_code.code)
|
|
180
|
+
except Exception:
|
|
181
|
+
msg = "Cannot start the DAQ6510 Control Server"
|
|
182
|
+
logger.exception(msg)
|
|
183
|
+
rich.print(f"[red]{msg}.")
|
|
141
184
|
|
|
142
185
|
return 0
|
|
143
186
|
|
|
144
187
|
|
|
145
|
-
@
|
|
188
|
+
@app.command()
|
|
146
189
|
def start_bg():
|
|
147
190
|
"""Starts the DAQ6510 Control Server in the background."""
|
|
148
191
|
|
|
149
|
-
|
|
192
|
+
print("Starting the DAQ6510 in the background is not implemented.")
|
|
150
193
|
|
|
151
194
|
|
|
152
|
-
@
|
|
195
|
+
@app.command()
|
|
153
196
|
def stop():
|
|
154
197
|
"""Sends a 'quit_server' command to the Keithley DAQ6510 Control Server."""
|
|
155
198
|
|
|
156
199
|
multiprocessing.current_process().name = "daq6510_cs (stop)"
|
|
157
200
|
|
|
201
|
+
from egse.env import setup_env
|
|
202
|
+
|
|
203
|
+
setup_env()
|
|
204
|
+
|
|
158
205
|
try:
|
|
159
206
|
with DAQ6510Proxy() as daq:
|
|
160
207
|
sp = daq.get_service_proxy()
|
|
@@ -165,17 +212,17 @@ def stop():
|
|
|
165
212
|
rich.print(f"[red]{msg}, could not send the Quit command. [black]Check log messages.")
|
|
166
213
|
|
|
167
214
|
|
|
168
|
-
@
|
|
215
|
+
@app.command()
|
|
169
216
|
def status():
|
|
170
217
|
"""Requests status information from the Control Server."""
|
|
171
218
|
|
|
172
219
|
multiprocessing.current_process().name = "daq6510_cs (status)"
|
|
173
220
|
|
|
174
|
-
|
|
175
|
-
hostname = CTRL_SETTINGS.HOSTNAME
|
|
176
|
-
port = CTRL_SETTINGS.COMMANDING_PORT
|
|
221
|
+
from egse.env import setup_env
|
|
177
222
|
|
|
178
|
-
|
|
223
|
+
setup_env()
|
|
224
|
+
|
|
225
|
+
endpoint = get_endpoint(SERVICE_TYPE, PROTOCOL, HOSTNAME, COMMANDING_PORT)
|
|
179
226
|
|
|
180
227
|
if is_control_server_active(endpoint):
|
|
181
228
|
rich.print("DAQ6510 CS: [green]active")
|
|
@@ -190,6 +237,4 @@ def status():
|
|
|
190
237
|
|
|
191
238
|
|
|
192
239
|
if __name__ == "__main__":
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
sys.exit(cli())
|
|
240
|
+
sys.exit(app())
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
__all__ = [
|
|
2
|
+
"DAQ6510",
|
|
3
|
+
"DAQ6510Command",
|
|
4
|
+
]
|
|
2
5
|
import socket
|
|
3
6
|
import time
|
|
4
7
|
|
|
5
8
|
from egse.command import ClientServerCommand
|
|
6
9
|
from egse.device import DeviceConnectionError
|
|
7
|
-
from egse.device import DeviceConnectionInterface
|
|
8
10
|
from egse.device import DeviceError
|
|
11
|
+
from egse.device import DeviceInterface
|
|
9
12
|
from egse.device import DeviceTimeoutError
|
|
10
13
|
from egse.device import DeviceTransport
|
|
14
|
+
from egse.log import logger
|
|
11
15
|
from egse.settings import Settings
|
|
12
|
-
from egse.system import Timer
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
16
17
|
IDENTIFICATION_QUERY = "*IDN?"
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
dev_settings = Settings.load("Keithley DAQ6510")
|
|
20
|
+
|
|
21
|
+
DEVICE_NAME = dev_settings.get("DEVICE_NAME", "DAQ6510")
|
|
22
|
+
DEV_HOST = dev_settings.get("HOSTNAME")
|
|
23
|
+
DEV_PORT = dev_settings.get("PORT")
|
|
24
|
+
READ_TIMEOUT = dev_settings.get("TIMEOUT") # [s], can be smaller than timeout (for DAQ6510Proxy) (e.g. 1s)
|
|
25
|
+
|
|
26
|
+
SEPARATOR = b"\n"
|
|
27
|
+
SEPARATOR_STR = SEPARATOR.decode()
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
class DAQ6510Command(ClientServerCommand):
|
|
@@ -32,13 +39,13 @@ class DAQ6510Command(ClientServerCommand):
|
|
|
32
39
|
"""
|
|
33
40
|
|
|
34
41
|
out = super().get_cmd_string(*args, **kwargs)
|
|
35
|
-
return out +
|
|
42
|
+
return out + SEPARATOR_STR
|
|
36
43
|
|
|
37
44
|
|
|
38
|
-
class
|
|
45
|
+
class DAQ6510(DeviceInterface, DeviceTransport):
|
|
39
46
|
"""Defines the low-level interface to the Keithley DAQ6510 Controller."""
|
|
40
47
|
|
|
41
|
-
def __init__(self, hostname: str =
|
|
48
|
+
def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT):
|
|
42
49
|
"""Initialisation of an Ethernet interface for the DAQ6510.
|
|
43
50
|
|
|
44
51
|
Args:
|
|
@@ -48,12 +55,66 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
|
48
55
|
|
|
49
56
|
super().__init__()
|
|
50
57
|
|
|
51
|
-
self.
|
|
52
|
-
self.
|
|
58
|
+
self.device_name = DEVICE_NAME
|
|
59
|
+
self.hostname = hostname
|
|
60
|
+
self.port = port
|
|
53
61
|
self._sock = None
|
|
54
62
|
|
|
55
63
|
self._is_connection_open = False
|
|
56
64
|
|
|
65
|
+
def initialize(self, commands: list[tuple[str, bool]] = None, reset_device: bool = False) -> list[str | None]:
|
|
66
|
+
"""Initialize the device with optional reset and command sequence.
|
|
67
|
+
|
|
68
|
+
Performs device initialization by optionally resetting the device and then
|
|
69
|
+
executing a sequence of commands. Each command can optionally expect a
|
|
70
|
+
response that will be logged for debugging purposes.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
commands: List of tuples containing (command_string, expects_response).
|
|
74
|
+
Each tuple specifies a command to send and whether to wait for and
|
|
75
|
+
log the response. Defaults to None (no commands executed).
|
|
76
|
+
reset_device: Whether to send a reset command (*RST) before executing
|
|
77
|
+
the command sequence. Defaults to False.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Response for each of the commands, or None when no response was expected.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
Any exceptions raised by the underlying write() or trans() methods,
|
|
84
|
+
typically communication errors or device timeouts.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
responses = device.initialize(
|
|
88
|
+
[
|
|
89
|
+
("*IDN?", True), # Query device ID, expect response
|
|
90
|
+
("SYST:ERR?", True), # Check for errors, expect response
|
|
91
|
+
("OUTP ON", False) # Enable output, no response expected
|
|
92
|
+
],
|
|
93
|
+
reset_device=True
|
|
94
|
+
)
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
commands = commands or []
|
|
98
|
+
responses = []
|
|
99
|
+
|
|
100
|
+
if reset_device:
|
|
101
|
+
logger.info(f"Resetting the {self.device_name}...")
|
|
102
|
+
self.write("*RST") # this also resets the user-defined buffer
|
|
103
|
+
|
|
104
|
+
for cmd, expects_response in commands:
|
|
105
|
+
if expects_response:
|
|
106
|
+
logger.debug(f"Sending {cmd}...")
|
|
107
|
+
response = self.trans(cmd).decode().strip()
|
|
108
|
+
logger.debug(f"{response = }")
|
|
109
|
+
else:
|
|
110
|
+
logger.debug(f"Sending {cmd}...")
|
|
111
|
+
self.write(cmd)
|
|
112
|
+
|
|
113
|
+
return responses
|
|
114
|
+
|
|
115
|
+
def is_simulator(self) -> bool:
|
|
116
|
+
return False
|
|
117
|
+
|
|
57
118
|
def connect(self) -> None:
|
|
58
119
|
"""Connects the device.
|
|
59
120
|
|
|
@@ -187,7 +248,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
|
187
248
|
"""
|
|
188
249
|
|
|
189
250
|
try:
|
|
190
|
-
command +=
|
|
251
|
+
command += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
|
|
191
252
|
|
|
192
253
|
self._sock.sendall(command.encode())
|
|
193
254
|
|
|
@@ -202,7 +263,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
|
202
263
|
raise DeviceConnectionError(DEVICE_NAME, msg)
|
|
203
264
|
raise
|
|
204
265
|
|
|
205
|
-
def trans(self, command: str) ->
|
|
266
|
+
def trans(self, command: str) -> bytes:
|
|
206
267
|
"""Sends a single command to the device controller and block until a response from the controller.
|
|
207
268
|
|
|
208
269
|
This is seen as a transaction.
|
|
@@ -221,7 +282,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
|
221
282
|
try:
|
|
222
283
|
# Attempt to send the complete command
|
|
223
284
|
|
|
224
|
-
command +=
|
|
285
|
+
command += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
|
|
225
286
|
|
|
226
287
|
self._sock.sendall(command.encode())
|
|
227
288
|
|
|
@@ -267,10 +328,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
|
|
|
267
328
|
break
|
|
268
329
|
except socket.timeout:
|
|
269
330
|
logger.warning(f"Socket timeout error for {self.hostname}:{self.port}")
|
|
270
|
-
return
|
|
271
|
-
except TimeoutError as exc:
|
|
272
|
-
logger.warning(f"Socket timeout error: {exc}")
|
|
273
|
-
return b"\r\n"
|
|
331
|
+
return SEPARATOR
|
|
274
332
|
finally:
|
|
275
333
|
self._sock.settimeout(saved_timeout)
|
|
276
334
|
|