keithley-tempcontrol 0.17.3__py3-none-any.whl → 0.17.4__py3-none-any.whl

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.
@@ -1,33 +1,40 @@
1
1
  import logging
2
2
  import re
3
3
  from pathlib import Path
4
- from typing import Dict, Union
4
+ from typing import Dict
5
5
  from typing import List
6
6
  from typing import Tuple
7
7
 
8
+ from egse.connect import get_endpoint
8
9
  from egse.decorators import dynamic_interface
9
10
  from egse.device import DeviceConnectionState
10
11
  from egse.device import DeviceInterface
11
- from egse.mixin import DynamicCommandMixin, CommandType
12
+ from egse.log import logger
13
+ from egse.mixin import CommandType
14
+ from egse.mixin import DynamicCommandMixin
12
15
  from egse.mixin import add_lf
13
16
  from egse.mixin import dynamic_command
14
17
  from egse.proxy import Proxy
15
18
  from egse.settings import Settings
16
- from egse.tempcontrol.keithley.daq6510_dev import DAQ6510EthernetInterface
17
- from egse.zmq_ser import connect_address
18
-
19
- logger = logging.getLogger(__name__)
19
+ from egse.tempcontrol.keithley.daq6510_dev import DAQ6510
20
20
 
21
21
  HERE = Path(__file__).parent
22
22
 
23
- CTRL_SETTINGS = Settings.load("Keithley Control Server")
24
- FW_SETTINGS = Settings.load("Keithley DAQ6510")
25
- DEVICE_SETTINGS = Settings.load(location=HERE, filename="daq6510.yaml")
23
+ cs_settings = Settings.load("Keithley Control Server")
24
+ dev_settings = Settings.load("Keithley DAQ6510")
26
25
 
26
+ PROTOCOL = cs_settings.get("PROTOCOL", "tcp")
27
+ HOSTNAME = cs_settings.get("HOSTNAME", "localhost")
28
+ COMMANDING_PORT = cs_settings.get("COMMANDING_PORT", 0)
29
+ TIMEOUT = cs_settings.get("TIMEOUT")
30
+ SERVICE_TYPE = cs_settings.get("SERVICE_TYPE", "daq6510")
27
31
 
28
32
  DEFAULT_BUFFER_1 = "defbuffer1"
29
33
  DEFAULT_BUFFER_2 = "defbuffer2"
30
34
 
35
+ DEV_HOST = dev_settings.get("HOSTNAME")
36
+ DEV_PORT = dev_settings.get("PORT")
37
+
31
38
 
32
39
  class DAQ6510Interface(DeviceInterface):
33
40
  """
@@ -35,7 +42,7 @@ class DAQ6510Interface(DeviceInterface):
35
42
  """
36
43
 
37
44
  @dynamic_interface
38
- def send_command(self, command: str, response: bool) -> Union[None, str]:
45
+ def send_command(self, command: str, response: bool) -> str | None:
39
46
  """Sends the given SCPI command to the device.
40
47
 
41
48
  The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
@@ -219,12 +226,12 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
219
226
  through an Ethernet interface.
220
227
  """
221
228
 
222
- def __init__(self, hostname: str = FW_SETTINGS.HOSTNAME, port: int = FW_SETTINGS.PORT):
229
+ def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT):
223
230
  """Opens a TCP/IP socket connection with the Keithley DAQ6510 Hardware.
224
231
 
225
232
  Args:
226
- hostname (str): IP address or fully qualified hostname of the Hexapod hardware controller. The default is
227
- defined in the ``settings.yaml`` configuration file.
233
+ hostname (str): IP address or fully qualified hostname of the DAQ6510 hardware controller.
234
+ The default is defined in the ``settings.yaml`` configuration file.
228
235
  port (int): IP port number to connect to, by default set in the ``settings.yaml`` configuration file.
229
236
 
230
237
  Raises:
@@ -235,7 +242,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
235
242
 
236
243
  logger.debug(f"Initializing the DAQ6510 Controller with hostname={hostname} on port={port}")
237
244
 
238
- self.daq = self.transport = DAQ6510EthernetInterface(hostname, port)
245
+ self.daq = self.transport = DAQ6510(hostname, port)
239
246
 
240
247
  # We set the default buffer here, this can be changed with the `create_buffer()` method.
241
248
 
@@ -277,7 +284,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
277
284
 
278
285
  return self.daq.is_connected()
279
286
 
280
- def send_command(self, command: str, response: bool) -> Union[None, str]:
287
+ def send_command(self, command: str, response: bool) -> str | None:
281
288
  """Sends an SCPI command to the device.
282
289
 
283
290
  The valid commands are described in the DAQ6510 Reference Manual [DAQ6510-901-01 Rev. B / September 2019].
@@ -292,7 +299,7 @@ class DAQ6510Controller(DAQ6510Interface, DynamicCommandMixin):
292
299
 
293
300
  return self.daq.trans(command) if response else self.daq.write(command)
294
301
 
295
- def read_buffer(self, start: int, end: int, buffer_name: str = DEFAULT_BUFFER_1, elements: List[str] = None):
302
+ def read_buffer(self, start: int, end: int, buffer_name: str = DEFAULT_BUFFER_1, elements: list[str] = None):
296
303
  """Reads specific data elements (measurements) from the given buffer.
297
304
 
298
305
  Elements that can be specified to read out:
@@ -566,10 +573,10 @@ class DAQ6510Proxy(Proxy, DAQ6510Interface):
566
573
 
567
574
  def __init__(
568
575
  self,
569
- protocol: str = 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)
576
+ protocol: str = PROTOCOL,
577
+ hostname: str = HOSTNAME,
578
+ port: int = COMMANDING_PORT,
579
+ timeout: float = TIMEOUT, # Timeout [s]: > scan count * interval + (one scan duration)
573
580
  ):
574
581
  """Initialisation of a DAQ6510Proxy.
575
582
 
@@ -578,10 +585,12 @@ class DAQ6510Proxy(Proxy, DAQ6510Interface):
578
585
  hostname (str): Location of the Control Server (IP address) [default is taken from settings file]
579
586
  port (int): TCP port on which the Control Server is listening for commands [default is taken from settings
580
587
  file]
581
- timeout (int): Timeout by which to establish the connection [ms]
588
+ timeout (float): Timeout by which to establish the connection [s]
582
589
  """
583
590
 
584
- super().__init__(connect_address(protocol, hostname, port), timeout=timeout)
591
+ endpoint = get_endpoint(SERVICE_TYPE, protocol, hostname, port)
592
+
593
+ super().__init__(endpoint, timeout=timeout)
585
594
 
586
595
 
587
596
  def create_channel_list(*args) -> str:
@@ -1,18 +1,28 @@
1
+ __all__ = [
2
+ "DAQ6510",
3
+ ]
4
+
1
5
  import asyncio
2
- import logging
3
6
  from typing import Any
4
7
  from typing import Dict
5
8
  from typing import Optional
6
9
 
10
+ from egse.log import logger
7
11
  from egse.scpi import AsyncSCPIInterface
12
+ from egse.settings import Settings
13
+
14
+ dev_settings = Settings.load("Keithley DAQ6510")
8
15
 
9
- logger = logging.getLogger(__name__)
16
+ DEV_HOST = dev_settings.get("HOSTNAME", "localhost")
17
+ DEV_PORT = dev_settings.get("PORT", 0)
18
+ DEVICE_NAME = dev_settings.get("DEVICE_NAME", "DAQ6510")
19
+ DEV_ID_VALIDATION = "DAQ6510"
10
20
 
11
21
 
12
22
  class DAQ6510(AsyncSCPIInterface):
13
23
  """Keithley DAQ6510 specific implementation."""
14
24
 
15
- def __init__(self, hostname: str, port: int = 5025, settings: Optional[Dict[str, Any]] = None):
25
+ def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT, settings: Optional[Dict[str, Any]] = None):
16
26
  """Initialize a Keithley DAQ6510 interface.
17
27
 
18
28
  Args:
@@ -21,45 +31,15 @@ class DAQ6510(AsyncSCPIInterface):
21
31
  settings: Additional device settings
22
32
  """
23
33
  super().__init__(
24
- device_name="DAQ6510",
34
+ device_name=DEVICE_NAME,
25
35
  hostname=hostname,
26
36
  port=port,
27
37
  settings=settings,
28
- id_validation="DAQ6510", # String that must appear in IDN? response
38
+ id_validation=DEV_ID_VALIDATION, # String that must appear in IDN? response
29
39
  )
30
40
 
31
41
  self._measurement_lock = asyncio.Lock()
32
42
 
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
43
  async def get_measurement(self, channel: str) -> float:
64
44
  """Get a measurement from a specific channel.
65
45
 
@@ -1,22 +1,29 @@
1
- import logging
2
1
  import multiprocessing
3
2
  import sys
4
3
 
5
- import click
6
- import invoke
7
4
  import rich
5
+ import typer
8
6
  import zmq
7
+
8
+ from egse.connect import get_endpoint
9
9
  from egse.control import ControlServer
10
10
  from egse.control import is_control_server_active
11
+ from egse.log import logger
12
+ from egse.logger import remote_logging
13
+ from egse.registry.client import RegistryClient
11
14
  from egse.settings import Settings
15
+ from egse.storage import store_housekeeping_information
12
16
  from egse.tempcontrol.keithley.daq6510 import DAQ6510Proxy
13
17
  from egse.tempcontrol.keithley.daq6510_protocol import DAQ6510Protocol
14
18
  from egse.zmq_ser import connect_address
15
- from prometheus_client import start_http_server
16
19
 
17
- logger = logging.getLogger(__name__)
20
+ cs_settings = Settings.load("Keithley Control Server")
18
21
 
19
- CTRL_SETTINGS = Settings.load("Keithley Control Server")
22
+ PROTOCOL = cs_settings.get("PROTOCOL", "tcp")
23
+ HOSTNAME = cs_settings.get("HOSTNAME", "localhost")
24
+ COMMANDING_PORT = cs_settings.get("COMMANDING_PORT", 0)
25
+ STORAGE_MNEMONIC = cs_settings.get("STORAGE_MNEMONIC", "DAQ6510")
26
+ SERVICE_TYPE = cs_settings.get("SERVICE_TYPE", "daq6510")
20
27
 
21
28
 
22
29
  def is_daq6510_cs_active(timeout: float = 0.5) -> bool:
@@ -28,7 +35,14 @@ def is_daq6510_cs_active(timeout: float = 0.5) -> bool:
28
35
  Returns: True if the Control Server is running and replied with the expected answer; False otherwise.
29
36
  """
30
37
 
31
- endpoint = connect_address(CTRL_SETTINGS.PROTOCOL, CTRL_SETTINGS.HOSTNAME, CTRL_SETTINGS.COMMANDING_PORT)
38
+ if COMMANDING_PORT == 0:
39
+ with RegistryClient() as client:
40
+ endpoint = client.get_endpoint(SERVICE_TYPE)
41
+ if endpoint is None:
42
+ logger.debug(f"No endpoint for {SERVICE_TYPE}")
43
+ return False
44
+ else:
45
+ endpoint = connect_address(PROTOCOL, HOSTNAME, COMMANDING_PORT)
32
46
 
33
47
  return is_control_server_active(endpoint, timeout)
34
48
 
@@ -62,13 +76,15 @@ class DAQ6510ControlServer(ControlServer):
62
76
 
63
77
  self.poller.register(self.dev_ctrl_cmd_sock, zmq.POLLIN)
64
78
 
79
+ self.register_service(service_type=cs_settings.SERVICE_TYPE)
80
+
65
81
  def get_communication_protocol(self) -> str:
66
82
  """Returns the communication protocol used by the Control Server.
67
83
 
68
84
  Returns: Communication protocol used by the Control Server, as specified in the settings.
69
85
  """
70
86
 
71
- return CTRL_SETTINGS.PROTOCOL
87
+ return cs_settings.PROTOCOL
72
88
 
73
89
  def get_commanding_port(self) -> int:
74
90
  """Returns the commanding port used by the Control Server.
@@ -76,7 +92,7 @@ class DAQ6510ControlServer(ControlServer):
76
92
  Returns: Commanding port used by the Control Server, as specified in the settings.
77
93
  """
78
94
 
79
- return CTRL_SETTINGS.COMMANDING_PORT
95
+ return cs_settings.COMMANDING_PORT
80
96
 
81
97
  def get_service_port(self):
82
98
  """Returns the service port used by the Control Server.
@@ -84,7 +100,7 @@ class DAQ6510ControlServer(ControlServer):
84
100
  Returns: Service port used by the Control Server, as specified in the settings.
85
101
  """
86
102
 
87
- return CTRL_SETTINGS.SERVICE_PORT
103
+ return cs_settings.SERVICE_PORT
88
104
 
89
105
  def get_monitoring_port(self):
90
106
  """Returns the monitoring port used by the Control Server.
@@ -92,7 +108,7 @@ class DAQ6510ControlServer(ControlServer):
92
108
  Returns: Monitoring port used by the Control Server, as specified in the settings.
93
109
  """
94
110
 
95
- return CTRL_SETTINGS.MONITORING_PORT
111
+ return cs_settings.MONITORING_PORT
96
112
 
97
113
  def get_storage_mnemonic(self):
98
114
  """Returns the storage mnemonics used by the Control Server.
@@ -104,57 +120,88 @@ class DAQ6510ControlServer(ControlServer):
104
120
  settings, "DAQ6510" will be used.
105
121
  """
106
122
 
107
- try:
108
- return CTRL_SETTINGS.STORAGE_MNEMONIC
109
- except AttributeError:
110
- return "DAQ6510"
123
+ return STORAGE_MNEMONIC
124
+
125
+ def is_storage_manager_active(self):
126
+ from egse.storage import is_storage_manager_active
127
+
128
+ return is_storage_manager_active()
129
+
130
+ def store_housekeeping_information(self, data):
131
+ """Send housekeeping information to the Storage manager."""
132
+
133
+ origin = self.get_storage_mnemonic()
134
+ store_housekeeping_information(origin, data)
135
+
136
+ def register_to_storage_manager(self):
137
+ from egse.storage import register_to_storage_manager
138
+ from egse.storage.persistence import TYPES
139
+
140
+ register_to_storage_manager(
141
+ origin=self.get_storage_mnemonic(),
142
+ persistence_class=TYPES["CSV"],
143
+ prep={
144
+ "column_names": list(self.device_protocol.get_housekeeping().keys()),
145
+ "mode": "a",
146
+ },
147
+ )
148
+
149
+ def unregister_from_storage_manager(self):
150
+ from egse.storage import unregister_from_storage_manager
151
+
152
+ unregister_from_storage_manager(origin=self.get_storage_mnemonic())
111
153
 
112
154
  def before_serve(self):
113
155
  """Steps to take before the Control Server is activated."""
114
156
 
115
- start_http_server(CTRL_SETTINGS.METRICS_PORT)
116
-
117
157
 
118
- @click.group()
119
- def cli():
120
- pass
158
+ app = typer.Typer(name="daq6510_cs")
121
159
 
122
160
 
123
- @cli.command()
161
+ @app.command()
124
162
  def start():
125
163
  """Starts the Keithley DAQ6510 Control Server."""
126
164
 
127
165
  multiprocessing.current_process().name = "daq6510_cs (start)"
128
166
 
129
- 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}.")
167
+ with remote_logging():
168
+ from egse.env import setup_env
169
+
170
+ setup_env()
171
+
172
+ try:
173
+ control_server = DAQ6510ControlServer()
174
+ control_server.serve()
175
+ except KeyboardInterrupt:
176
+ logger.debug("Shutdown requested...exiting")
177
+ except SystemExit as exit_code:
178
+ logger.debug("System Exit with code {}.".format(exit_code))
179
+ sys.exit(exit_code.code)
180
+ except Exception:
181
+ msg = "Cannot start the DAQ6510 Control Server"
182
+ logger.exception(msg)
183
+ rich.print(f"[red]{msg}.")
141
184
 
142
185
  return 0
143
186
 
144
187
 
145
- @cli.command()
188
+ @app.command()
146
189
  def start_bg():
147
190
  """Starts the DAQ6510 Control Server in the background."""
148
191
 
149
- invoke.run("daq6510_cs start", disown=True)
192
+ print("Starting the DAQ6510 in the background is not implemented.")
150
193
 
151
194
 
152
- @cli.command()
195
+ @app.command()
153
196
  def stop():
154
197
  """Sends a 'quit_server' command to the Keithley DAQ6510 Control Server."""
155
198
 
156
199
  multiprocessing.current_process().name = "daq6510_cs (stop)"
157
200
 
201
+ from egse.env import setup_env
202
+
203
+ setup_env()
204
+
158
205
  try:
159
206
  with DAQ6510Proxy() as daq:
160
207
  sp = daq.get_service_proxy()
@@ -165,17 +212,17 @@ def stop():
165
212
  rich.print(f"[red]{msg}, could not send the Quit command. [black]Check log messages.")
166
213
 
167
214
 
168
- @cli.command()
215
+ @app.command()
169
216
  def status():
170
217
  """Requests status information from the Control Server."""
171
218
 
172
219
  multiprocessing.current_process().name = "daq6510_cs (status)"
173
220
 
174
- protocol = CTRL_SETTINGS.PROTOCOL
175
- hostname = CTRL_SETTINGS.HOSTNAME
176
- port = CTRL_SETTINGS.COMMANDING_PORT
221
+ from egse.env import setup_env
177
222
 
178
- endpoint = connect_address(protocol, hostname, port)
223
+ setup_env()
224
+
225
+ endpoint = get_endpoint(SERVICE_TYPE, PROTOCOL, HOSTNAME, COMMANDING_PORT)
179
226
 
180
227
  if is_control_server_active(endpoint):
181
228
  rich.print("DAQ6510 CS: [green]active")
@@ -190,6 +237,4 @@ def status():
190
237
 
191
238
 
192
239
  if __name__ == "__main__":
193
- logging.basicConfig(level=logging.DEBUG, format=Settings.LOG_FORMAT_FULL)
194
-
195
- sys.exit(cli())
240
+ sys.exit(app())
@@ -1,23 +1,30 @@
1
- import logging
1
+ __all__ = [
2
+ "DAQ6510",
3
+ "DAQ6510Command",
4
+ ]
2
5
  import socket
3
6
  import time
4
7
 
5
8
  from egse.command import ClientServerCommand
6
9
  from egse.device import DeviceConnectionError
7
- from egse.device import DeviceConnectionInterface
8
10
  from egse.device import DeviceError
11
+ from egse.device import DeviceInterface
9
12
  from egse.device import DeviceTimeoutError
10
13
  from egse.device import DeviceTransport
14
+ from egse.log import logger
11
15
  from egse.settings import Settings
12
- from egse.system import Timer
13
-
14
- logger = logging.getLogger(__name__)
15
16
 
16
17
  IDENTIFICATION_QUERY = "*IDN?"
17
18
 
18
- 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)
19
+ dev_settings = Settings.load("Keithley DAQ6510")
20
+
21
+ DEVICE_NAME = dev_settings.get("DEVICE_NAME", "DAQ6510")
22
+ DEV_HOST = dev_settings.get("HOSTNAME")
23
+ DEV_PORT = dev_settings.get("PORT")
24
+ READ_TIMEOUT = dev_settings.get("TIMEOUT") # [s], can be smaller than timeout (for DAQ6510Proxy) (e.g. 1s)
25
+
26
+ SEPARATOR = b"\n"
27
+ SEPARATOR_STR = SEPARATOR.decode()
21
28
 
22
29
 
23
30
  class DAQ6510Command(ClientServerCommand):
@@ -32,13 +39,13 @@ class DAQ6510Command(ClientServerCommand):
32
39
  """
33
40
 
34
41
  out = super().get_cmd_string(*args, **kwargs)
35
- return out + "\n"
42
+ return out + SEPARATOR_STR
36
43
 
37
44
 
38
- class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
45
+ class DAQ6510(DeviceInterface, DeviceTransport):
39
46
  """Defines the low-level interface to the Keithley DAQ6510 Controller."""
40
47
 
41
- def __init__(self, hostname: str = None, port: int = None):
48
+ def __init__(self, hostname: str = DEV_HOST, port: int = DEV_PORT):
42
49
  """Initialisation of an Ethernet interface for the DAQ6510.
43
50
 
44
51
  Args:
@@ -48,12 +55,66 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
48
55
 
49
56
  super().__init__()
50
57
 
51
- self.hostname = DEVICE_SETTINGS.HOSTNAME if hostname is None else hostname
52
- self.port = DEVICE_SETTINGS.PORT if port is None else port
58
+ self.device_name = DEVICE_NAME
59
+ self.hostname = hostname
60
+ self.port = port
53
61
  self._sock = None
54
62
 
55
63
  self._is_connection_open = False
56
64
 
65
+ def initialize(self, commands: list[tuple[str, bool]] = None, reset_device: bool = False) -> list[str | None]:
66
+ """Initialize the device with optional reset and command sequence.
67
+
68
+ Performs device initialization by optionally resetting the device and then
69
+ executing a sequence of commands. Each command can optionally expect a
70
+ response that will be logged for debugging purposes.
71
+
72
+ Args:
73
+ commands: List of tuples containing (command_string, expects_response).
74
+ Each tuple specifies a command to send and whether to wait for and
75
+ log the response. Defaults to None (no commands executed).
76
+ reset_device: Whether to send a reset command (*RST) before executing
77
+ the command sequence. Defaults to False.
78
+
79
+ Returns:
80
+ Response for each of the commands, or None when no response was expected.
81
+
82
+ Raises:
83
+ Any exceptions raised by the underlying write() or trans() methods,
84
+ typically communication errors or device timeouts.
85
+
86
+ Example:
87
+ responses = device.initialize(
88
+ [
89
+ ("*IDN?", True), # Query device ID, expect response
90
+ ("SYST:ERR?", True), # Check for errors, expect response
91
+ ("OUTP ON", False) # Enable output, no response expected
92
+ ],
93
+ reset_device=True
94
+ )
95
+ """
96
+
97
+ commands = commands or []
98
+ responses = []
99
+
100
+ if reset_device:
101
+ logger.info(f"Resetting the {self.device_name}...")
102
+ self.write("*RST") # this also resets the user-defined buffer
103
+
104
+ for cmd, expects_response in commands:
105
+ if expects_response:
106
+ logger.debug(f"Sending {cmd}...")
107
+ response = self.trans(cmd).decode().strip()
108
+ logger.debug(f"{response = }")
109
+ else:
110
+ logger.debug(f"Sending {cmd}...")
111
+ self.write(cmd)
112
+
113
+ return responses
114
+
115
+ def is_simulator(self) -> bool:
116
+ return False
117
+
57
118
  def connect(self) -> None:
58
119
  """Connects the device.
59
120
 
@@ -187,7 +248,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
187
248
  """
188
249
 
189
250
  try:
190
- command += "\n" if not command.endswith("\n") else ""
251
+ command += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
191
252
 
192
253
  self._sock.sendall(command.encode())
193
254
 
@@ -202,7 +263,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
202
263
  raise DeviceConnectionError(DEVICE_NAME, msg)
203
264
  raise
204
265
 
205
- def trans(self, command: str) -> str:
266
+ def trans(self, command: str) -> bytes:
206
267
  """Sends a single command to the device controller and block until a response from the controller.
207
268
 
208
269
  This is seen as a transaction.
@@ -221,7 +282,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
221
282
  try:
222
283
  # Attempt to send the complete command
223
284
 
224
- command += "\n" if not command.endswith("\n") else ""
285
+ command += SEPARATOR_STR if not command.endswith(SEPARATOR_STR) else ""
225
286
 
226
287
  self._sock.sendall(command.encode())
227
288
 
@@ -267,10 +328,7 @@ class DAQ6510EthernetInterface(DeviceConnectionInterface, DeviceTransport):
267
328
  break
268
329
  except socket.timeout:
269
330
  logger.warning(f"Socket timeout error for {self.hostname}:{self.port}")
270
- return b"\r\n"
271
- except TimeoutError as exc:
272
- logger.warning(f"Socket timeout error: {exc}")
273
- return b"\r\n"
331
+ return SEPARATOR
274
332
  finally:
275
333
  self._sock.settimeout(saved_timeout)
276
334
 
@@ -1,22 +1,31 @@
1
1
  import asyncio
2
2
  import datetime
3
3
  import json
4
- import logging
5
4
  import signal
6
5
  import time
7
6
  from asyncio import Task
8
7
  from pathlib import Path
9
8
  from typing import Any
9
+ from typing import Callable
10
10
  from typing import Optional
11
11
 
12
+ import typer
12
13
  import zmq
13
14
  import zmq.asyncio
14
15
 
15
16
  from egse.device import DeviceConnectionError
16
17
  from egse.device import DeviceTimeoutError
18
+ from egse.log import logger
19
+ from egse.settings import Settings
20
+ from egse.system import TyperAsyncCommand
17
21
  from egse.tempcontrol.keithley.daq6510_adev import DAQ6510
18
22
 
19
- logger = logging.getLogger("daq6510-mon")
23
+ settings = Settings.load("Keithley DAQ6510")
24
+
25
+ DAQ_DEV_HOST = settings.get("HOSTNAME")
26
+ DAQ_DEV_PORT = settings.get("PORT")
27
+
28
+ DAQ_MON_CMD_PORT = 5556
20
29
 
21
30
 
22
31
  class DAQ6510Monitor:
@@ -28,8 +37,8 @@ class DAQ6510Monitor:
28
37
  def __init__(
29
38
  self,
30
39
  daq_hostname: str,
31
- daq_port: int = 5025,
32
- zmq_port: int = 5556,
40
+ daq_port: int = DAQ_DEV_PORT,
41
+ zmq_port: int = DAQ_MON_CMD_PORT,
33
42
  log_file: str = "temperature_readings.log",
34
43
  channels: list[str] = None,
35
44
  poll_interval: float = 60.0,
@@ -60,7 +69,7 @@ class DAQ6510Monitor:
60
69
  self.running = False
61
70
  self.polling_active = False
62
71
  self.daq_interface = None
63
- self.command_handlers = {
72
+ self.command_handlers: dict[str, Callable] = {
64
73
  "START_POLLING": self._handle_start_polling,
65
74
  "STOP_POLLING": self._handle_stop_polling,
66
75
  "SET_INTERVAL": self._handle_set_interval,
@@ -78,6 +87,8 @@ class DAQ6510Monitor:
78
87
  self.log_file.parent.mkdir(exist_ok=True, parents=True)
79
88
 
80
89
  # 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.
81
92
  self.daq_interface = DAQ6510(hostname=daq_hostname, port=daq_port)
82
93
 
83
94
  async def start(self):
@@ -85,9 +96,12 @@ class DAQ6510Monitor:
85
96
  logger.info(f"Starting DAQ6510 Monitoring Service on ZMQ port {self.zmq_port}")
86
97
  self.running = True
87
98
 
99
+ def handle_shutdown():
100
+ asyncio.create_task(self.shutdown())
101
+
88
102
  # Register signal handlers for graceful shutdown
89
103
  for sig in (signal.SIGINT, signal.SIGTERM):
90
- asyncio.get_event_loop().add_signal_handler(sig, lambda: asyncio.create_task(self.shutdown()))
104
+ asyncio.get_event_loop().add_signal_handler(sig, handle_shutdown)
91
105
 
92
106
  # Start the main service tasks
93
107
  await asyncio.gather(self.command_listener(), self.connect_daq(), return_exceptions=True)
@@ -101,11 +115,29 @@ class DAQ6510Monitor:
101
115
  async def connect_daq(self):
102
116
  """Establish connection to the DAQ6510."""
103
117
  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
+
104
136
  try:
105
137
  logger.info(f"Connecting to DAQ6510 at {self.daq_hostname}:{self.daq_port}")
106
138
  await self.daq_interface.connect()
107
139
  logger.info("Successfully connected to DAQ6510.")
108
- await self.daq_interface.initialize()
140
+ await self.daq_interface.initialize(commands=init_commands, reset_device=True)
109
141
  logger.info("Successfully initialized DAQ6510 for measurements.")
110
142
 
111
143
  # If we were polling before, restart it.
@@ -367,9 +399,9 @@ class DAQ6510Monitor:
367
399
 
368
400
 
369
401
  class DAQMonitorClient:
370
- """Simple client for interacting with the DAQ Monitor Service."""
402
+ """A simple client for interacting with the DAQ Monitor Service."""
371
403
 
372
- def __init__(self, server_address: str = "localhost", port: int = 5556, timeout: float = 5.0):
404
+ def __init__(self, server_address: str = "localhost", port: int = DAQ_MON_CMD_PORT, timeout: float = 5.0):
373
405
  """Initialize the client.
374
406
 
375
407
  Args:
@@ -412,7 +444,7 @@ class DAQMonitorClient:
412
444
  params: Optional command parameters
413
445
 
414
446
  Returns:
415
- Response from the service
447
+ Response from the service as a dictionary.
416
448
  """
417
449
  params = params or {}
418
450
  message = {"command": command, "params": params}
@@ -488,8 +520,10 @@ class DAQMonitorClient:
488
520
  def get_status(self) -> dict[str, Any]:
489
521
  """Get current service status.
490
522
 
523
+ To confirm the status is 'ok', check the response for the key 'status'.
524
+
491
525
  Returns:
492
- Status information
526
+ Status information as dictionary.
493
527
  """
494
528
  return self._send_command("GET_STATUS")
495
529
 
@@ -502,12 +536,19 @@ class DAQMonitorClient:
502
536
  return self._send_command("SHUTDOWN")
503
537
 
504
538
 
505
- async def main():
539
+ app = typer.Typer(name="daq6510_mon")
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
+ """
506
547
  monitor = DAQ6510Monitor(
507
- daq_hostname="192.168.68.77",
508
- daq_port=5025,
509
- zmq_port=5556,
510
- log_file="temperature_readings.log",
548
+ daq_hostname=DAQ_DEV_HOST,
549
+ daq_port=DAQ_DEV_PORT,
550
+ zmq_port=DAQ_MON_CMD_PORT,
551
+ log_file=log_file,
511
552
  channels=["101", "102"],
512
553
  poll_interval=10.0,
513
554
  )
@@ -516,9 +557,4 @@ async def main():
516
557
 
517
558
 
518
559
  if __name__ == "__main__":
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())
560
+ asyncio.run(app())
@@ -1,21 +1,20 @@
1
1
  import logging
2
+ from pathlib import Path
2
3
 
3
4
  from egse.control import ControlServer
4
5
  from egse.device import DeviceTimeoutError
5
- from egse.hk import read_conversion_dict
6
- from egse.metrics import define_metrics
7
6
  from egse.protocol import CommandProtocol
8
7
  from egse.settings import Settings
9
8
  from egse.setup import load_setup
10
- from egse.synoptics import SynopticsManagerProxy
11
9
  from egse.system import format_datetime
12
10
  from egse.tempcontrol.keithley.daq6510 import DAQ6510Controller
13
11
  from egse.tempcontrol.keithley.daq6510 import DAQ6510Interface
14
- from egse.tempcontrol.keithley.daq6510 import DAQ6510Simulator
15
12
  from egse.tempcontrol.keithley.daq6510_dev import DAQ6510Command
16
13
  from egse.zmq_ser import bind_address
17
14
 
18
- COMMAND_SETTINGS = Settings.load(filename="daq6510.yaml")
15
+ HERE = Path(__file__).parent
16
+
17
+ COMMAND_SETTINGS = Settings.load(location=HERE, filename="daq6510.yaml")
19
18
 
20
19
  MODULE_LOGGER = logging.getLogger(__name__)
21
20
 
@@ -28,13 +27,9 @@ class DAQ6510Protocol(CommandProtocol):
28
27
  control_server: Control Server for which to send out status and monitoring information
29
28
  """
30
29
 
31
- super().__init__()
32
- self.control_server = control_server
30
+ super().__init__(control_server)
33
31
 
34
- if Settings.simulation_mode():
35
- self.daq = DAQ6510Simulator()
36
- else:
37
- self.daq = DAQ6510Controller()
32
+ self.daq = DAQ6510Controller()
38
33
 
39
34
  try:
40
35
  self.daq.connect()
@@ -47,16 +42,12 @@ class DAQ6510Protocol(CommandProtocol):
47
42
  setup = load_setup()
48
43
  self.channels = setup.gse.DAQ6510.channels
49
44
 
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
-
55
45
  def get_bind_address(self) -> str:
56
- """Returns a string with the bind address, the endpoint, for accepting connections and bind a socket to.
57
-
46
+ """
47
+ Returns a string with the bind address, the endpoint, for accepting connections and bind a socket to.
58
48
 
59
- Returns: String with the protocol and port to bind a socket to.
49
+ Returns:
50
+ String with the protocol and port to bind a socket to.
60
51
  """
61
52
 
62
53
  return bind_address(
@@ -65,34 +56,26 @@ class DAQ6510Protocol(CommandProtocol):
65
56
  )
66
57
 
67
58
  def get_status(self) -> dict:
68
- """Returns a dictionary with status information for the Control Server and the DAQ6510.
59
+ """
60
+ Returns a dictionary with status information for the Control Server and the DAQ6510.
69
61
 
70
- Returns: Dictionary with status information for the Control Server and the DAQ6510.
62
+ Returns:
63
+ Dictionary with status information for the Control Server and the DAQ6510.
71
64
  """
72
65
 
73
66
  return super().get_status()
74
67
 
75
68
  def get_housekeeping(self) -> dict:
76
- """Returns a dictionary with housekeeping information about the DAQ6510.
69
+ """
70
+ Returns a dictionary with housekeeping information about the DAQ6510.
77
71
 
78
- Returns: Dictionary with housekeeping information about the DAQ6510.
72
+ Returns:
73
+ Dictionary with housekeeping information about the DAQ6510.
79
74
  """
80
75
 
81
76
  hk_dict = dict()
82
77
  hk_dict["timestamp"] = format_datetime()
83
78
 
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
-
96
79
  return hk_dict
97
80
 
98
81
  def quit(self) -> None:
@@ -1,21 +1,32 @@
1
- from __future__ import annotations
2
-
3
1
  import contextlib
4
2
  import datetime
5
- import logging
6
3
  import re
7
4
  import socket
8
5
  import time
6
+ from functools import partial
7
+ from typing import Annotated
9
8
 
10
9
  import typer
10
+
11
+ from egse.env import bool_env
12
+ from egse.log import logging
11
13
  from egse.settings import Settings
12
14
  from egse.system import SignalCatcher
13
15
 
14
- logger = logging.getLogger("daq6510-sim")
16
+ logger = logging.getLogger("egse.daq6510-sim")
15
17
 
18
+ VERSION = "0.1.0"
19
+ VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG")
16
20
  HOST = "localhost"
17
21
  DAQ_SETTINGS = Settings.load("Keithley DAQ6510")
18
22
 
23
+ READ_TIMEOUT = 2.0
24
+ """The timeout set on the connection socket, applicable when reading from the socket with `recv`."""
25
+ CONNECTION_TIMEOUT = 2.0
26
+ """The timeout set on the socket before accepting a connection."""
27
+
28
+ SEPARATOR = b"\n"
29
+ SEPARATOR_STR = SEPARATOR.decode()
19
30
 
20
31
  device_time = datetime.datetime.now(datetime.timezone.utc)
21
32
  reference_time = device_time
@@ -23,8 +34,8 @@ reference_time = device_time
23
34
 
24
35
  app = typer.Typer(help="DAQ6510 Simulator")
25
36
 
26
- error_msg: str | None = None
27
- """Global error message, always contains the last error. Reset in the inner loop of run_simulator."""
37
+ error_msg: str = ""
38
+ """Global error message, always contains the last error. Reset to an empty string in the inner loop of run_simulator."""
28
39
 
29
40
 
30
41
  def create_datetime(year, month, day, hour, minute, second):
@@ -62,8 +73,14 @@ def reset():
62
73
  logger.info("RESET")
63
74
 
64
75
 
76
+ def log(level: int, msg: str):
77
+ logger.log(level, msg)
78
+
79
+
65
80
  COMMAND_ACTIONS_RESPONSES = {
66
- "*IDN?": (None, "KEITHLEY INSTRUMENTS, MODEL DAQ6510, SIMULATOR"),
81
+ "*IDN?": (None, f"KEITHLEY INSTRUMENTS,DAQ6510,SIMULATOR,{VERSION}"),
82
+ "*ACTION-RESPONSE?": (partial(log, logging.INFO, "Requested action with response."), get_time),
83
+ "*ACTION-NO-RESPONSE": (partial(log, logging.INFO, "Requested action without response."), None),
67
84
  }
68
85
 
69
86
  # Check the regex at https://regex101.com
@@ -79,65 +96,105 @@ COMMAND_PATTERNS_ACTIONS_RESPONSES = {
79
96
 
80
97
 
81
98
  def write(conn, response: str):
82
- response = f"{response}\n".encode()
83
- logger.debug(f"write: {response = }")
99
+ response = f"{response}{SEPARATOR_STR}".encode()
100
+ if VERBOSE_DEBUG:
101
+ logger.debug(f"write: {response = }")
84
102
  conn.sendall(response)
85
103
 
86
104
 
105
+ # Keep a receive buffer per connection
106
+ _recv_buffers: dict[int, bytes] = {}
107
+
108
+
87
109
  def read(conn) -> str:
88
110
  """
89
- Reads one command string from the socket, i.e. until a linefeed ('\n') is received.
90
-
91
- Returns:
92
- The command string with the linefeed stripped off.
111
+ Read bytes from `conn` until a `SEPARATOR` is found (or connection closed / timeout).
112
+ Returns the first chunk (separator stripped). Any bytes after the separator are kept
113
+ in a per-connection buffer for the next call.
93
114
  """
94
-
95
- n_total = 0
96
- buf_size = 1024 * 4
97
- command_string = bytes()
115
+ fileno = conn.fileno()
116
+ buf = _recv_buffers.get(fileno, b"")
98
117
 
99
118
  try:
100
- for _ in range(100):
101
- data = conn.recv(buf_size)
102
- n = len(data)
103
- n_total += n
104
- command_string += data
105
- # if data.endswith(b'\n'):
106
- if n < buf_size:
107
- break
119
+ while True:
120
+ # If we already have a full line in the buffer, split and return it.
121
+ if SEPARATOR in buf:
122
+ line, rest = buf.split(SEPARATOR, 1)
123
+ _recv_buffers[fileno] = rest
124
+ logger.info(f"read: {line=}")
125
+ return line.decode().rstrip()
126
+
127
+ # Read more data
128
+ data = conn.recv(1024 * 4)
129
+ if not data:
130
+ # Connection closed by peer; return whatever we have (may be empty)
131
+ _recv_buffers.pop(fileno, None)
132
+ logger.info(f"read (connection closed): {buf=}")
133
+ return buf.decode().rstrip()
134
+ buf += data
135
+ _recv_buffers[fileno] = buf
136
+
108
137
  except socket.timeout:
109
- # This timeout is caught at the caller, where the timeout is set.
138
+ # If we have accumulated data without a separator, return it (partial read),
139
+ # otherwise propagate the timeout so caller can handle/suppress it.
140
+ if buf:
141
+ _recv_buffers[fileno] = buf
142
+ logger.info(f"read (timeout, partial): {buf=}")
143
+ return buf.decode().rstrip()
110
144
  raise
111
145
 
112
- logger.info(f"read: {command_string=}")
113
-
114
- return command_string.decode().rstrip()
115
-
116
146
 
117
- def process_command(command_string: str) -> str:
147
+ def process_command(command_string: str) -> str | None:
148
+ """Process the given command string and return a response."""
118
149
  global COMMAND_ACTIONS_RESPONSES
119
150
  global COMMAND_PATTERNS_ACTIONS_RESPONSES
120
151
  global error_msg
121
152
 
122
- # LOGGER.debug(f"{command_string=}")
153
+ if VERBOSE_DEBUG:
154
+ logger.debug(f"{command_string=}")
123
155
 
124
156
  try:
125
157
  action, response = COMMAND_ACTIONS_RESPONSES[command_string]
126
- action and action()
127
- if error_msg:
128
- return error_msg
158
+ if VERBOSE_DEBUG:
159
+ logger.debug(f"{action=}, {response=}")
160
+
161
+ if action:
162
+ action()
163
+
164
+ if response:
165
+ if error_msg:
166
+ return error_msg
167
+ else:
168
+ return response() if callable(response) else response
129
169
  else:
130
- return response if isinstance(response, str) else response()
170
+ if error_msg:
171
+ logger.error(f"Error occurred during process command: {error_msg}")
172
+ return None
131
173
  except KeyError:
132
174
  # try to match with a value
133
175
  for key, value in COMMAND_PATTERNS_ACTIONS_RESPONSES.items():
134
176
  if match := re.match(key, command_string, flags=re.IGNORECASE):
135
- # LOGGER.debug(f"{match=}, {match.groups()}")
177
+ if VERBOSE_DEBUG:
178
+ logger.debug(f"{match=}, {match.groups()}")
136
179
  action, response = value
137
- # LOGGER.debug(f"{action=}, {response=}")
138
- action and action(*match.groups())
139
- return error_msg or (response if isinstance(response, str) or response is None else response())
140
- return f"ERROR: unknown command string: {command_string}"
180
+ if VERBOSE_DEBUG:
181
+ logger.debug(f"{action=}, {response=}")
182
+
183
+ if action:
184
+ action(*match.groups())
185
+
186
+ if response:
187
+ if error_msg:
188
+ return error_msg
189
+ else:
190
+ return response() if callable(response) else response
191
+ else:
192
+ if error_msg:
193
+ logger.error(f"Error occurred during process command: {error_msg}")
194
+ return None
195
+
196
+ logger.error(f"ERROR: unknown command string: {command_string}")
197
+ return None
141
198
 
142
199
 
143
200
  def run_simulator():
@@ -150,7 +207,7 @@ def run_simulator():
150
207
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
151
208
  s.bind((HOST, DAQ_SETTINGS.PORT))
152
209
  s.listen()
153
- s.settimeout(2.0)
210
+ s.settimeout(CONNECTION_TIMEOUT)
154
211
  while True:
155
212
  while True:
156
213
  with contextlib.suppress(socket.timeout):
@@ -161,13 +218,17 @@ def run_simulator():
161
218
  with conn:
162
219
  logger.info(f"Accepted connection from {addr}")
163
220
  write(conn, "This is PLATO DAQ6510 X.X.sim")
164
- conn.settimeout(2.0)
221
+ conn.settimeout(READ_TIMEOUT)
165
222
  try:
166
223
  while True:
167
224
  error_msg = ""
168
225
  with contextlib.suppress(socket.timeout):
169
226
  data = read(conn)
170
- logger.info(f"{data = }")
227
+ if VERBOSE_DEBUG:
228
+ logger.debug(f"{data = }")
229
+ if not data:
230
+ logger.info("Client closed connection, accepting new connection...")
231
+ break
171
232
  if data.strip() == "STOP":
172
233
  logger.info("Client requested to terminate...")
173
234
  s.close()
@@ -176,9 +237,6 @@ def run_simulator():
176
237
  response = process_command(cmd.strip())
177
238
  if response is not None:
178
239
  write(conn, response)
179
- if not data:
180
- logger.info("Client closed connection, accepting new connection...")
181
- break
182
240
  if killer.term_signal_received:
183
241
  logger.info("Terminating...")
184
242
  s.close()
@@ -196,20 +254,20 @@ def run_simulator():
196
254
  logger.info(f"{exc.__class__.__name__} caught: {exc.args}")
197
255
 
198
256
 
199
- def send_request(cmd: str, type_: str = "query"):
200
- from egse.tempcontrol.keithley.daq6510_dev import DAQ6510EthernetInterface
257
+ def send_request(cmd: str, cmd_type: str = "query") -> str | None:
258
+ from egse.tempcontrol.keithley.daq6510_dev import DAQ6510
201
259
 
202
260
  response = None
203
261
 
204
- daq_dev = DAQ6510EthernetInterface(hostname="localhost", port=5025)
262
+ daq_dev = DAQ6510(hostname="localhost", port=5025)
205
263
  daq_dev.connect()
206
264
 
207
- if type_.lower().strip() == "query":
265
+ if cmd_type.lower().strip() == "query":
208
266
  response = daq_dev.query(cmd)
209
- elif type_.lower().strip() == "write":
267
+ elif cmd_type.lower().strip() == "write":
210
268
  daq_dev.write(cmd)
211
269
  else:
212
- logger.info(f"Unknown type {type_} for send_request.")
270
+ logger.info(f"Unknown command type {cmd_type} for send_request.")
213
271
 
214
272
  daq_dev.disconnect()
215
273
 
@@ -234,15 +292,14 @@ def stop():
234
292
 
235
293
 
236
294
  @app.command()
237
- def command(type_: str, cmd: str):
238
- response = send_request(cmd, type_)
295
+ def command(
296
+ cmd: str,
297
+ cmd_type: Annotated[str, typer.Argument(help="either 'write', 'query'")] = "query",
298
+ ):
299
+ """Send an SCPI command directly to the simulator. The response will be in the log info."""
300
+ response = send_request(cmd, cmd_type)
239
301
  logger.info(f"{response}")
240
302
 
241
303
 
242
304
  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
-
248
305
  app()
@@ -2,14 +2,13 @@
2
2
  #
3
3
  import subprocess
4
4
  import sys
5
- from pathlib import Path
6
5
 
7
6
  import rich
8
7
  import typer
9
8
 
10
- daq6510 = typer.Typer(
11
- name="daq6510", help="DAQ6510 Data Acquisition Unit, Keithley, temperature monitoring", no_args_is_help=True
12
- )
9
+ from egse.system import redirect_output_to_log
10
+
11
+ daq6510 = typer.Typer(name="daq6510", help="DAQ6510 Data Acquisition Unit, Keithley, temperature monitoring")
13
12
 
14
13
 
15
14
  @daq6510.command(name="start")
@@ -17,19 +16,39 @@ def start_daq6510():
17
16
  """Start the daq6510 service."""
18
17
  rich.print("Starting service daq6510")
19
18
 
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
+
20
29
 
21
30
  @daq6510.command(name="stop")
22
31
  def stop_daq6510():
23
32
  """Stop the daq6510 service."""
24
33
  rich.print("Terminating service daq6510")
25
34
 
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
+
26
45
 
27
46
  @daq6510.command(name="status")
28
47
  def status_daq6510():
29
48
  """Print status information on the daq6510 service."""
30
49
 
31
50
  proc = subprocess.Popen(
32
- [sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "status"],
51
+ [sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_cs", "status"],
33
52
  stdout=subprocess.PIPE,
34
53
  stderr=subprocess.PIPE,
35
54
  stdin=subprocess.DEVNULL,
@@ -47,7 +66,7 @@ def start_daq6510_sim():
47
66
  """Start the DAQ6510 Simulator."""
48
67
  rich.print("Starting service DAQ6510 Simulator")
49
68
 
50
- out = open(Path("~/.daq6510_sim.start.out").expanduser(), "w")
69
+ out = redirect_output_to_log("daq6510_sim.start.log")
51
70
 
52
71
  subprocess.Popen(
53
72
  [sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "start"],
@@ -63,7 +82,7 @@ def stop_daq6510_sim():
63
82
  """Stop the DAQ6510 Simulator."""
64
83
  rich.print("Terminating the DAQ6510 simulator.")
65
84
 
66
- out = open(Path("~/.daq6510_sim.stop.out").expanduser(), "w")
85
+ out = redirect_output_to_log("daq6510_sim.stop.log")
67
86
 
68
87
  subprocess.Popen(
69
88
  [sys.executable, "-m", "egse.tempcontrol.keithley.daq6510_sim", "stop"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keithley-tempcontrol
3
- Version: 0.17.3
3
+ Version: 0.17.4
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>
@@ -0,0 +1,17 @@
1
+ egse/tempcontrol/keithley/__init__.py,sha256=QMm0vy6OMqzWmJZ1K6IwKSOpgYeCmUdbcRhv75LH9ZY,130
2
+ egse/tempcontrol/keithley/daq6510.py,sha256=pe12HIC2Yav5ZCGYecoQzhypYcCwecEaJpWZn-OHi8A,24867
3
+ egse/tempcontrol/keithley/daq6510.yaml,sha256=dHHVNyUpOQpdrZpnxPbT6slsl-8Gbnhifj4Q8QOfOYg,4400
4
+ egse/tempcontrol/keithley/daq6510_adev.py,sha256=Pp5U6a0FrUs1TFLqzvGQHhb3vfw6bhtAt4uX0OVRjBI,2263
5
+ egse/tempcontrol/keithley/daq6510_cs.py,sha256=Ga7z8S6z0oTxL_qQP8FXPaNKlJ6o9RrsPFOXeIJ2TT4,7700
6
+ egse/tempcontrol/keithley/daq6510_dev.py,sha256=EKFFDhP8-FdIPBPosc1DfzE3h4DQmNU1d0Fi7Yhx4I4,12906
7
+ egse/tempcontrol/keithley/daq6510_mon.py,sha256=Xbn2U-l9uxPwNN1-aYW72oJodL2sx13suCiPPbDSti0,20932
8
+ egse/tempcontrol/keithley/daq6510_protocol.py,sha256=v8FUrxEm7bnRzM_iQzW0mMCHTgAMZw4f2Ronl8fdKIE,2676
9
+ egse/tempcontrol/keithley/daq6510_sim.py,sha256=Ys5jroT-2i-V_qCR0L6yOYM9CiktnNbB3LvVLGwaD8A,9917
10
+ keithley_tempcontrol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ keithley_tempcontrol/cgse_explore.py,sha256=y_FkFxJW0vdqGNp9yTU0ELBKxby74-ev3fTuf99Vl1s,400
12
+ keithley_tempcontrol/cgse_services.py,sha256=tndviv2rvygkNSsGy1oA43VfFpyVkdB9If-9sVlLbK4,2466
13
+ keithley_tempcontrol/settings.yaml,sha256=wbrgSZQAdqFl6AxiLJIN36UsdiVHQCzdsgi7Hs7dv7o,1467
14
+ keithley_tempcontrol-0.17.4.dist-info/METADATA,sha256=D8Uz0pSzzRUVMpVz05Uhy-4l_u21GF-eryb17PkqhZs,962
15
+ keithley_tempcontrol-0.17.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ keithley_tempcontrol-0.17.4.dist-info/entry_points.txt,sha256=_0j2BwcwPi4LlRrhvEWfp9GO9KT8WhCkJe2gFgMzOPs,491
17
+ keithley_tempcontrol-0.17.4.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- egse/tempcontrol/keithley/__init__.py,sha256=QMm0vy6OMqzWmJZ1K6IwKSOpgYeCmUdbcRhv75LH9ZY,130
2
- egse/tempcontrol/keithley/daq6510.py,sha256=QA3K0xJ_-bsY6D4jzOyeDHz1uYSrgf8Z8O-9RynCB5E,24711
3
- egse/tempcontrol/keithley/daq6510.yaml,sha256=dHHVNyUpOQpdrZpnxPbT6slsl-8Gbnhifj4Q8QOfOYg,4400
4
- egse/tempcontrol/keithley/daq6510_adev.py,sha256=kodJGonsy_whr15XbVC5CA94mMqm1036FGK0DXmZtxo,3482
5
- egse/tempcontrol/keithley/daq6510_cs.py,sha256=YVmUtEkGZ0ZKQ7BtvloL_da_fSy_rVr0EB1V2SjLdq0,6089
6
- egse/tempcontrol/keithley/daq6510_dev.py,sha256=ZjzDU3x7XYRLRoNK9lcqE-t32XDMt0IHW26GO87xt98,10780
7
- egse/tempcontrol/keithley/daq6510_mon.py,sha256=uox4IaHKwC_hXDQpj4sGEnZyK-dKXjqQdZ2yiaCbt_c,19103
8
- egse/tempcontrol/keithley/daq6510_protocol.py,sha256=fdcJxsOFYaEDVIY4XzLVC4PSIAL20gKj8os8f0nsSh4,3655
9
- egse/tempcontrol/keithley/daq6510_sim.py,sha256=GHC7NY2NZ5fm20AjMx7glSGWY1OOynndHUnCPcs5MZM,7568
10
- keithley_tempcontrol/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- keithley_tempcontrol/cgse_explore.py,sha256=y_FkFxJW0vdqGNp9yTU0ELBKxby74-ev3fTuf99Vl1s,400
12
- keithley_tempcontrol/cgse_services.py,sha256=H62jEzwRQMvKjkefFw9mSQTYRnT8ga_-SdJ26gPaUE8,1960
13
- keithley_tempcontrol/settings.yaml,sha256=wbrgSZQAdqFl6AxiLJIN36UsdiVHQCzdsgi7Hs7dv7o,1467
14
- keithley_tempcontrol-0.17.3.dist-info/METADATA,sha256=JhlNFJaYoCoFBeh8A3boUq3XSNPLxJs72_0NrW7BgcY,962
15
- keithley_tempcontrol-0.17.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- keithley_tempcontrol-0.17.3.dist-info/entry_points.txt,sha256=_0j2BwcwPi4LlRrhvEWfp9GO9KT8WhCkJe2gFgMzOPs,491
17
- keithley_tempcontrol-0.17.3.dist-info/RECORD,,