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