keithley-tempcontrol 0.17.1__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.1 → keithley_tempcontrol-0.17.3}/.gitignore +5 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/PKG-INFO +1 -1
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/pyproject.toml +1 -1
- {keithley_tempcontrol-0.17.1 → 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.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_cs.py +44 -89
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_dev.py +20 -78
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_mon.py +22 -58
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_protocol.py +35 -18
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_sim.py +18 -19
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/cgse_services.py +7 -26
- keithley_tempcontrol-0.17.1/src/egse/tempcontrol/keithley/daq6510_acs.py +0 -3
- keithley_tempcontrol-0.17.1/src/egse/tempcontrol/keithley/daq6510_adev.py +0 -77
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/README.md +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/justfile +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/noxfile.py +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/service_registry.db +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/__init__.py +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510.yaml +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/__init__.py +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/cgse_explore.py +0 -0
- {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/settings.yaml +0 -0
{keithley_tempcontrol-0.17.1 → 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
|
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import datetime
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import signal
|
|
5
6
|
import time
|
|
6
7
|
from asyncio import Task
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
|
-
from typing import Callable
|
|
10
10
|
from typing import Optional
|
|
11
11
|
|
|
12
|
-
import typer
|
|
13
12
|
import zmq
|
|
14
13
|
import zmq.asyncio
|
|
15
14
|
|
|
16
15
|
from egse.device import DeviceConnectionError
|
|
17
16
|
from egse.device import DeviceTimeoutError
|
|
18
|
-
from egse.log import logger
|
|
19
|
-
from egse.settings import Settings
|
|
20
|
-
from egse.system import TyperAsyncCommand
|
|
21
17
|
from egse.tempcontrol.keithley.daq6510_adev import DAQ6510
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
DAQ_DEV_HOST = settings.get("HOSTNAME")
|
|
26
|
-
DAQ_DEV_PORT = settings.get("PORT")
|
|
27
|
-
|
|
28
|
-
DAQ_MON_CMD_PORT = 5556
|
|
19
|
+
logger = logging.getLogger("daq6510-mon")
|
|
29
20
|
|
|
30
21
|
|
|
31
22
|
class DAQ6510Monitor:
|
|
@@ -37,8 +28,8 @@ class DAQ6510Monitor:
|
|
|
37
28
|
def __init__(
|
|
38
29
|
self,
|
|
39
30
|
daq_hostname: str,
|
|
40
|
-
daq_port: int =
|
|
41
|
-
zmq_port: int =
|
|
31
|
+
daq_port: int = 5025,
|
|
32
|
+
zmq_port: int = 5556,
|
|
42
33
|
log_file: str = "temperature_readings.log",
|
|
43
34
|
channels: list[str] = None,
|
|
44
35
|
poll_interval: float = 60.0,
|
|
@@ -69,7 +60,7 @@ class DAQ6510Monitor:
|
|
|
69
60
|
self.running = False
|
|
70
61
|
self.polling_active = False
|
|
71
62
|
self.daq_interface = None
|
|
72
|
-
self.command_handlers
|
|
63
|
+
self.command_handlers = {
|
|
73
64
|
"START_POLLING": self._handle_start_polling,
|
|
74
65
|
"STOP_POLLING": self._handle_stop_polling,
|
|
75
66
|
"SET_INTERVAL": self._handle_set_interval,
|
|
@@ -87,8 +78,6 @@ class DAQ6510Monitor:
|
|
|
87
78
|
self.log_file.parent.mkdir(exist_ok=True, parents=True)
|
|
88
79
|
|
|
89
80
|
# Create DAQ interface
|
|
90
|
-
# In this case we use the device itself, no control server. That means
|
|
91
|
-
# the monitoring must be the only service connecting to the device.
|
|
92
81
|
self.daq_interface = DAQ6510(hostname=daq_hostname, port=daq_port)
|
|
93
82
|
|
|
94
83
|
async def start(self):
|
|
@@ -96,12 +85,9 @@ class DAQ6510Monitor:
|
|
|
96
85
|
logger.info(f"Starting DAQ6510 Monitoring Service on ZMQ port {self.zmq_port}")
|
|
97
86
|
self.running = True
|
|
98
87
|
|
|
99
|
-
def handle_shutdown():
|
|
100
|
-
asyncio.create_task(self.shutdown())
|
|
101
|
-
|
|
102
88
|
# Register signal handlers for graceful shutdown
|
|
103
89
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
104
|
-
asyncio.get_event_loop().add_signal_handler(sig,
|
|
90
|
+
asyncio.get_event_loop().add_signal_handler(sig, lambda: asyncio.create_task(self.shutdown()))
|
|
105
91
|
|
|
106
92
|
# Start the main service tasks
|
|
107
93
|
await asyncio.gather(self.command_listener(), self.connect_daq(), return_exceptions=True)
|
|
@@ -115,29 +101,11 @@ class DAQ6510Monitor:
|
|
|
115
101
|
async def connect_daq(self):
|
|
116
102
|
"""Establish connection to the DAQ6510."""
|
|
117
103
|
while self.running:
|
|
118
|
-
init_commands = [
|
|
119
|
-
('TRAC:MAKE "test1", 1000', False), # create a new buffer
|
|
120
|
-
# settings for channel 1 and 2 of slot 1
|
|
121
|
-
('SENS:FUNC "TEMP", (@101:102)', False), # set the function to temperature
|
|
122
|
-
("SENS:TEMP:TRAN FRTD, (@101)", False), # set the transducer to 4-wire RTD
|
|
123
|
-
("SENS:TEMP:RTD:FOUR PT100, (@101)", False), # set the type of the 4-wire RTD
|
|
124
|
-
("SENS:TEMP:TRAN RTD, (@102)", False), # set the transducer to 2-wire RTD
|
|
125
|
-
("SENS:TEMP:RTD:TWO PT100, (@102)", False), # set the type of the 2-wire RTD
|
|
126
|
-
('ROUT:SCAN:BUFF "test1"', False),
|
|
127
|
-
("ROUT:SCAN:CRE (@101:102)", False),
|
|
128
|
-
("ROUT:CHAN:OPEN (@101:102)", False),
|
|
129
|
-
("ROUT:STAT? (@101:102)", True),
|
|
130
|
-
("ROUT:SCAN:STAR:STIM NONE", False),
|
|
131
|
-
# ("ROUT:SCAN:ADD:SING (@101, 102)", False), # not sure what this does, not really needed
|
|
132
|
-
("ROUT:SCAN:COUN:SCAN 1", False), # not sure if this is needed in this setting
|
|
133
|
-
# ("ROUT:SCAN:INT 1", False),
|
|
134
|
-
]
|
|
135
|
-
|
|
136
104
|
try:
|
|
137
105
|
logger.info(f"Connecting to DAQ6510 at {self.daq_hostname}:{self.daq_port}")
|
|
138
106
|
await self.daq_interface.connect()
|
|
139
107
|
logger.info("Successfully connected to DAQ6510.")
|
|
140
|
-
await self.daq_interface.initialize(
|
|
108
|
+
await self.daq_interface.initialize()
|
|
141
109
|
logger.info("Successfully initialized DAQ6510 for measurements.")
|
|
142
110
|
|
|
143
111
|
# If we were polling before, restart it.
|
|
@@ -399,9 +367,9 @@ class DAQ6510Monitor:
|
|
|
399
367
|
|
|
400
368
|
|
|
401
369
|
class DAQMonitorClient:
|
|
402
|
-
"""
|
|
370
|
+
"""Simple client for interacting with the DAQ Monitor Service."""
|
|
403
371
|
|
|
404
|
-
def __init__(self, server_address: str = "localhost", port: int =
|
|
372
|
+
def __init__(self, server_address: str = "localhost", port: int = 5556, timeout: float = 5.0):
|
|
405
373
|
"""Initialize the client.
|
|
406
374
|
|
|
407
375
|
Args:
|
|
@@ -444,7 +412,7 @@ class DAQMonitorClient:
|
|
|
444
412
|
params: Optional command parameters
|
|
445
413
|
|
|
446
414
|
Returns:
|
|
447
|
-
Response from the service
|
|
415
|
+
Response from the service
|
|
448
416
|
"""
|
|
449
417
|
params = params or {}
|
|
450
418
|
message = {"command": command, "params": params}
|
|
@@ -520,10 +488,8 @@ class DAQMonitorClient:
|
|
|
520
488
|
def get_status(self) -> dict[str, Any]:
|
|
521
489
|
"""Get current service status.
|
|
522
490
|
|
|
523
|
-
To confirm the status is 'ok', check the response for the key 'status'.
|
|
524
|
-
|
|
525
491
|
Returns:
|
|
526
|
-
Status information
|
|
492
|
+
Status information
|
|
527
493
|
"""
|
|
528
494
|
return self._send_command("GET_STATUS")
|
|
529
495
|
|
|
@@ -536,19 +502,12 @@ class DAQMonitorClient:
|
|
|
536
502
|
return self._send_command("SHUTDOWN")
|
|
537
503
|
|
|
538
504
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
@app.command(cls=TyperAsyncCommand, name="monitor")
|
|
543
|
-
async def main(log_file: str = "temperature_readings.log"):
|
|
544
|
-
"""
|
|
545
|
-
Start the DAQ6510 monitoring app in the background.
|
|
546
|
-
"""
|
|
505
|
+
async def main():
|
|
547
506
|
monitor = DAQ6510Monitor(
|
|
548
|
-
daq_hostname=
|
|
549
|
-
daq_port=
|
|
550
|
-
zmq_port=
|
|
551
|
-
log_file=
|
|
507
|
+
daq_hostname="192.168.68.77",
|
|
508
|
+
daq_port=5025,
|
|
509
|
+
zmq_port=5556,
|
|
510
|
+
log_file="temperature_readings.log",
|
|
552
511
|
channels=["101", "102"],
|
|
553
512
|
poll_interval=10.0,
|
|
554
513
|
)
|
|
@@ -557,4 +516,9 @@ async def main(log_file: str = "temperature_readings.log"):
|
|
|
557
516
|
|
|
558
517
|
|
|
559
518
|
if __name__ == "__main__":
|
|
560
|
-
|
|
519
|
+
logging.basicConfig(
|
|
520
|
+
level=logging.DEBUG,
|
|
521
|
+
format="[%(asctime)s] %(threadName)-12s %(levelname)-8s %(name)-12s %(lineno)5d:%(module)-20s %(message)s",
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
asyncio.run(main())
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from pathlib import Path
|
|
3
2
|
|
|
4
3
|
from egse.control import ControlServer
|
|
5
4
|
from egse.device import DeviceTimeoutError
|
|
5
|
+
from egse.hk import read_conversion_dict
|
|
6
|
+
from egse.metrics import define_metrics
|
|
6
7
|
from egse.protocol import CommandProtocol
|
|
7
8
|
from egse.settings import Settings
|
|
8
9
|
from egse.setup import load_setup
|
|
10
|
+
from egse.synoptics import SynopticsManagerProxy
|
|
9
11
|
from egse.system import format_datetime
|
|
10
12
|
from egse.tempcontrol.keithley.daq6510 import DAQ6510Controller
|
|
11
13
|
from egse.tempcontrol.keithley.daq6510 import DAQ6510Interface
|
|
14
|
+
from egse.tempcontrol.keithley.daq6510 import DAQ6510Simulator
|
|
12
15
|
from egse.tempcontrol.keithley.daq6510_dev import DAQ6510Command
|
|
13
16
|
from egse.zmq_ser import bind_address
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
COMMAND_SETTINGS = Settings.load(location=HERE, filename="daq6510.yaml")
|
|
18
|
+
COMMAND_SETTINGS = Settings.load(filename="daq6510.yaml")
|
|
18
19
|
|
|
19
20
|
MODULE_LOGGER = logging.getLogger(__name__)
|
|
20
21
|
|
|
@@ -27,9 +28,13 @@ class DAQ6510Protocol(CommandProtocol):
|
|
|
27
28
|
control_server: Control Server for which to send out status and monitoring information
|
|
28
29
|
"""
|
|
29
30
|
|
|
30
|
-
super().__init__(
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.control_server = control_server
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
if Settings.simulation_mode():
|
|
35
|
+
self.daq = DAQ6510Simulator()
|
|
36
|
+
else:
|
|
37
|
+
self.daq = DAQ6510Controller()
|
|
33
38
|
|
|
34
39
|
try:
|
|
35
40
|
self.daq.connect()
|
|
@@ -42,12 +47,16 @@ class DAQ6510Protocol(CommandProtocol):
|
|
|
42
47
|
setup = load_setup()
|
|
43
48
|
self.channels = setup.gse.DAQ6510.channels
|
|
44
49
|
|
|
50
|
+
self.hk_conversion_table = read_conversion_dict(self.control_server.get_storage_mnemonic(), setup=setup)
|
|
51
|
+
|
|
52
|
+
self.synoptics = SynopticsManagerProxy()
|
|
53
|
+
self.metrics = define_metrics(origin="DAS-DAQ6510", use_site=True, setup=setup)
|
|
54
|
+
|
|
45
55
|
def get_bind_address(self) -> str:
|
|
46
|
-
"""
|
|
47
|
-
Returns a string with the bind address, the endpoint, for accepting connections and bind a socket to.
|
|
56
|
+
"""Returns a string with the bind address, the endpoint, for accepting connections and bind a socket to.
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
|
|
59
|
+
Returns: String with the protocol and port to bind a socket to.
|
|
51
60
|
"""
|
|
52
61
|
|
|
53
62
|
return bind_address(
|
|
@@ -56,26 +65,34 @@ class DAQ6510Protocol(CommandProtocol):
|
|
|
56
65
|
)
|
|
57
66
|
|
|
58
67
|
def get_status(self) -> dict:
|
|
59
|
-
"""
|
|
60
|
-
Returns a dictionary with status information for the Control Server and the DAQ6510.
|
|
68
|
+
"""Returns a dictionary with status information for the Control Server and the DAQ6510.
|
|
61
69
|
|
|
62
|
-
Returns:
|
|
63
|
-
Dictionary with status information for the Control Server and the DAQ6510.
|
|
70
|
+
Returns: Dictionary with status information for the Control Server and the DAQ6510.
|
|
64
71
|
"""
|
|
65
72
|
|
|
66
73
|
return super().get_status()
|
|
67
74
|
|
|
68
75
|
def get_housekeeping(self) -> dict:
|
|
69
|
-
"""
|
|
70
|
-
Returns a dictionary with housekeeping information about the DAQ6510.
|
|
76
|
+
"""Returns a dictionary with housekeeping information about the DAQ6510.
|
|
71
77
|
|
|
72
|
-
Returns:
|
|
73
|
-
Dictionary with housekeeping information about the DAQ6510.
|
|
78
|
+
Returns: Dictionary with housekeeping information about the DAQ6510.
|
|
74
79
|
"""
|
|
75
80
|
|
|
76
81
|
hk_dict = dict()
|
|
77
82
|
hk_dict["timestamp"] = format_datetime()
|
|
78
83
|
|
|
84
|
+
# # TODO I guess we have to do something along those lines
|
|
85
|
+
# # (We'll have to increase the HK delay, cfr. Agilents)
|
|
86
|
+
# measurement = self.daq.perform_measurement(channel_list=self.channels)
|
|
87
|
+
# temperatures = convert_hk_names(measurement, self.hk_conversion_table)
|
|
88
|
+
# hk_dict.update(temperatures)
|
|
89
|
+
#
|
|
90
|
+
# self.synoptics.store_th_synoptics(hk_dict)
|
|
91
|
+
#
|
|
92
|
+
# for key, value in hk_dict.items():
|
|
93
|
+
# if key != "timestamp":
|
|
94
|
+
# self.metrics[key].set(value)
|
|
95
|
+
|
|
79
96
|
return hk_dict
|
|
80
97
|
|
|
81
98
|
def quit(self) -> None:
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import contextlib
|
|
2
4
|
import datetime
|
|
5
|
+
import logging
|
|
3
6
|
import re
|
|
4
7
|
import socket
|
|
5
8
|
import time
|
|
6
|
-
from typing import Annotated
|
|
7
9
|
|
|
8
10
|
import typer
|
|
9
|
-
|
|
10
|
-
from egse.log import logging
|
|
11
11
|
from egse.settings import Settings
|
|
12
12
|
from egse.system import SignalCatcher
|
|
13
13
|
|
|
14
|
-
logger = logging.getLogger("
|
|
14
|
+
logger = logging.getLogger("daq6510-sim")
|
|
15
15
|
|
|
16
16
|
HOST = "localhost"
|
|
17
17
|
DAQ_SETTINGS = Settings.load("Keithley DAQ6510")
|
|
18
18
|
|
|
19
|
-
SEPARATOR = b"\n"
|
|
20
|
-
SEPARATOR_STR = SEPARATOR.decode()
|
|
21
19
|
|
|
22
20
|
device_time = datetime.datetime.now(datetime.timezone.utc)
|
|
23
21
|
reference_time = device_time
|
|
@@ -81,7 +79,7 @@ COMMAND_PATTERNS_ACTIONS_RESPONSES = {
|
|
|
81
79
|
|
|
82
80
|
|
|
83
81
|
def write(conn, response: str):
|
|
84
|
-
response = f"{response}
|
|
82
|
+
response = f"{response}\n".encode()
|
|
85
83
|
logger.debug(f"write: {response = }")
|
|
86
84
|
conn.sendall(response)
|
|
87
85
|
|
|
@@ -198,20 +196,20 @@ def run_simulator():
|
|
|
198
196
|
logger.info(f"{exc.__class__.__name__} caught: {exc.args}")
|
|
199
197
|
|
|
200
198
|
|
|
201
|
-
def send_request(cmd: str,
|
|
202
|
-
from egse.tempcontrol.keithley.daq6510_dev import
|
|
199
|
+
def send_request(cmd: str, type_: str = "query"):
|
|
200
|
+
from egse.tempcontrol.keithley.daq6510_dev import DAQ6510EthernetInterface
|
|
203
201
|
|
|
204
202
|
response = None
|
|
205
203
|
|
|
206
|
-
daq_dev =
|
|
204
|
+
daq_dev = DAQ6510EthernetInterface(hostname="localhost", port=5025)
|
|
207
205
|
daq_dev.connect()
|
|
208
206
|
|
|
209
|
-
if
|
|
207
|
+
if type_.lower().strip() == "query":
|
|
210
208
|
response = daq_dev.query(cmd)
|
|
211
|
-
elif
|
|
209
|
+
elif type_.lower().strip() == "write":
|
|
212
210
|
daq_dev.write(cmd)
|
|
213
211
|
else:
|
|
214
|
-
logger.info(f"Unknown
|
|
212
|
+
logger.info(f"Unknown type {type_} for send_request.")
|
|
215
213
|
|
|
216
214
|
daq_dev.disconnect()
|
|
217
215
|
|
|
@@ -236,14 +234,15 @@ def stop():
|
|
|
236
234
|
|
|
237
235
|
|
|
238
236
|
@app.command()
|
|
239
|
-
def command(
|
|
240
|
-
cmd
|
|
241
|
-
cmd_type: Annotated[str, typer.Argument(help="either 'write', 'query'")] = "query",
|
|
242
|
-
):
|
|
243
|
-
"""Send an SCPI command directly to the simulator. The response will be in the log info."""
|
|
244
|
-
response = send_request(cmd, cmd_type)
|
|
237
|
+
def command(type_: str, cmd: str):
|
|
238
|
+
response = send_request(cmd, type_)
|
|
245
239
|
logger.info(f"{response}")
|
|
246
240
|
|
|
247
241
|
|
|
248
242
|
if __name__ == "__main__":
|
|
243
|
+
logging.basicConfig(
|
|
244
|
+
level=logging.DEBUG,
|
|
245
|
+
format="%(asctime)s %(threadName)-12s %(levelname)-8s %(name)-12s %(module)-20s %(message)s",
|
|
246
|
+
)
|
|
247
|
+
|
|
249
248
|
app()
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
#
|
|
3
3
|
import subprocess
|
|
4
4
|
import sys
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import rich
|
|
7
8
|
import typer
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
daq6510 = typer.Typer(
|
|
11
|
+
name="daq6510", help="DAQ6510 Data Acquisition Unit, Keithley, temperature monitoring", no_args_is_help=True
|
|
12
|
+
)
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@daq6510.command(name="start")
|
|
@@ -16,39 +17,19 @@ def start_daq6510():
|
|
|
16
17
|
"""Start the daq6510 service."""
|
|
17
18
|
rich.print("Starting service daq6510")
|
|
18
19
|
|
|
19
|
-
out = redirect_output_to_log("daq6510_cs.start.log")
|
|
20
|
-
|
|
21
|
-
subprocess.Popen(
|
|
22
|
-
[sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_cs", "start"],
|
|
23
|
-
stdout=out,
|
|
24
|
-
stderr=out,
|
|
25
|
-
stdin=subprocess.DEVNULL,
|
|
26
|
-
close_fds=True,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
20
|
|
|
30
21
|
@daq6510.command(name="stop")
|
|
31
22
|
def stop_daq6510():
|
|
32
23
|
"""Stop the daq6510 service."""
|
|
33
24
|
rich.print("Terminating service daq6510")
|
|
34
25
|
|
|
35
|
-
out = redirect_output_to_log("daq6510_cs.stop.log")
|
|
36
|
-
|
|
37
|
-
subprocess.Popen(
|
|
38
|
-
[sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_cs", "stop"],
|
|
39
|
-
stdout=out,
|
|
40
|
-
stderr=out,
|
|
41
|
-
stdin=subprocess.DEVNULL,
|
|
42
|
-
close_fds=True,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
26
|
|
|
46
27
|
@daq6510.command(name="status")
|
|
47
28
|
def status_daq6510():
|
|
48
29
|
"""Print status information on the daq6510 service."""
|
|
49
30
|
|
|
50
31
|
proc = subprocess.Popen(
|
|
51
|
-
[sys.executable, "-m", "egse.tempcontrol.keithley.
|
|
32
|
+
[sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "status"],
|
|
52
33
|
stdout=subprocess.PIPE,
|
|
53
34
|
stderr=subprocess.PIPE,
|
|
54
35
|
stdin=subprocess.DEVNULL,
|
|
@@ -66,7 +47,7 @@ def start_daq6510_sim():
|
|
|
66
47
|
"""Start the DAQ6510 Simulator."""
|
|
67
48
|
rich.print("Starting service DAQ6510 Simulator")
|
|
68
49
|
|
|
69
|
-
out =
|
|
50
|
+
out = open(Path("~/.daq6510_sim.start.out").expanduser(), "w")
|
|
70
51
|
|
|
71
52
|
subprocess.Popen(
|
|
72
53
|
[sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "start"],
|
|
@@ -82,7 +63,7 @@ def stop_daq6510_sim():
|
|
|
82
63
|
"""Stop the DAQ6510 Simulator."""
|
|
83
64
|
rich.print("Terminating the DAQ6510 simulator.")
|
|
84
65
|
|
|
85
|
-
out =
|
|
66
|
+
out = open(Path("~/.daq6510_sim.stop.out").expanduser(), "w")
|
|
86
67
|
|
|
87
68
|
subprocess.Popen(
|
|
88
69
|
[sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "stop"],
|
|
@@ -1,77 +0,0 @@
|
|
|
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")
|
|
17
|
-
DEV_PORT = dev_settings.get("PORT")
|
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/__init__.py
RENAMED
|
File without changes
|
{keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/cgse_explore.py
RENAMED
|
File without changes
|
{keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/settings.yaml
RENAMED
|
File without changes
|