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.
Files changed (22) hide show
  1. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/PKG-INFO +1 -1
  2. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/pyproject.toml +1 -1
  3. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510.py +22 -31
  4. keithley_tempcontrol-0.17.3/src/egse/tempcontrol/keithley/daq6510_adev.py +97 -0
  5. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_cs.py +44 -89
  6. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_dev.py +20 -78
  7. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_mon.py +22 -58
  8. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_protocol.py +35 -18
  9. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510_sim.py +60 -117
  10. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/cgse_services.py +7 -26
  11. keithley_tempcontrol-0.17.2/src/egse/tempcontrol/keithley/daq6510_acs.py +0 -3
  12. keithley_tempcontrol-0.17.2/src/egse/tempcontrol/keithley/daq6510_adev.py +0 -77
  13. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/.gitignore +0 -0
  14. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/README.md +0 -0
  15. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/justfile +0 -0
  16. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/noxfile.py +0 -0
  17. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/service_registry.db +0 -0
  18. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/__init__.py +0 -0
  19. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/egse/tempcontrol/keithley/daq6510.yaml +0 -0
  20. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/__init__.py +0 -0
  21. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/cgse_explore.py +0 -0
  22. {keithley_tempcontrol-0.17.2 → keithley_tempcontrol-0.17.3}/src/keithley_tempcontrol/settings.yaml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keithley-tempcontrol
3
- Version: 0.17.2
3
+ Version: 0.17.3
4
4
  Summary: Keithley Temperature Control for CGSE
5
5
  Author: IvS KU Leuven
6
6
  Maintainer-email: Rik Huygen <rik.huygen@kuleuven.be>, Sara Regibo <sara.regibo@kuleuven.be>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "keithley-tempcontrol"
3
- version = "0.17.2"
3
+ version = "0.17.3"
4
4
  description = "Keithley Temperature Control for CGSE"
5
5
  authors = [
6
6
  {name = "IvS KU Leuven"}
@@ -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.log import logger
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 DAQ6510
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
- cs_settings = Settings.load("Keithley Control Server")
24
- dev_settings = Settings.load("Keithley DAQ6510")
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 | None:
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 = DEV_HOST, port: int = DEV_PORT):
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 DAQ6510 hardware controller.
234
- The default is defined in the ``settings.yaml`` configuration file.
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 = DAQ6510(hostname, port)
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 | None:
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: list[str] = None):
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: float = TIMEOUT, # Timeout [s]: > scan count * interval + (one scan duration)
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 (float): Timeout by which to establish the connection [s]
581
+ timeout (int): Timeout by which to establish the connection [ms]
589
582
  """
590
583
 
591
- endpoint = get_endpoint(SERVICE_TYPE, protocol, hostname, port)
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
- cs_settings = Settings.load("Keithley Control Server")
17
+ logger = logging.getLogger(__name__)
21
18
 
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")
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
- 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)
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 cs_settings.PROTOCOL
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 cs_settings.COMMANDING_PORT
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 cs_settings.SERVICE_PORT
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 cs_settings.MONITORING_PORT
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
- 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())
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
- app = typer.Typer(name="daq6510_cs")
118
+ @click.group()
119
+ def cli():
120
+ pass
159
121
 
160
122
 
161
- @app.command()
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
- 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}.")
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
- @app.command()
145
+ @cli.command()
189
146
  def start_bg():
190
147
  """Starts the DAQ6510 Control Server in the background."""
191
148
 
192
- print("Starting the DAQ6510 in the background is not implemented.")
149
+ invoke.run("daq6510_cs start", disown=True)
193
150
 
194
151
 
195
- @app.command()
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
- @app.command()
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
- from egse.env import setup_env
174
+ protocol = CTRL_SETTINGS.PROTOCOL
175
+ hostname = CTRL_SETTINGS.HOSTNAME
176
+ port = CTRL_SETTINGS.COMMANDING_PORT
222
177
 
223
- setup_env()
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
- sys.exit(app())
193
+ logging.basicConfig(level=logging.DEBUG, format=Settings.LOG_FORMAT_FULL)
194
+
195
+ sys.exit(cli())
@@ -1,30 +1,23 @@
1
- __all__ = [
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
- IDENTIFICATION_QUERY = "*IDN?"
18
-
19
- dev_settings = Settings.load("Keithley DAQ6510")
14
+ logger = logging.getLogger(__name__)
20
15
 
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)
16
+ IDENTIFICATION_QUERY = "*IDN?"
25
17
 
26
- SEPARATOR = b"\n"
27
- SEPARATOR_STR = SEPARATOR.decode()
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 + SEPARATOR_STR
35
+ return out + "\n"
43
36
 
44
37
 
45
- class DAQ6510(DeviceInterface, DeviceTransport):
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 = DEV_HOST, port: int = DEV_PORT):
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.device_name = DEVICE_NAME
59
- self.hostname = hostname
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 += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
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) -> bytes:
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 += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
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 SEPARATOR
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