keithley-tempcontrol 0.17.1__tar.gz → 0.17.2__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 (21) hide show
  1. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/.gitignore +5 -0
  2. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/PKG-INFO +1 -1
  3. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/pyproject.toml +1 -1
  4. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_sim.py +98 -42
  5. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/README.md +0 -0
  6. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/justfile +0 -0
  7. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/noxfile.py +0 -0
  8. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/service_registry.db +0 -0
  9. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/__init__.py +0 -0
  10. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510.py +0 -0
  11. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510.yaml +0 -0
  12. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_acs.py +0 -0
  13. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_adev.py +0 -0
  14. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_cs.py +0 -0
  15. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_dev.py +0 -0
  16. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_mon.py +0 -0
  17. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/egse/tempcontrol/keithley/daq6510_protocol.py +0 -0
  18. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/keithley_tempcontrol/__init__.py +0 -0
  19. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/keithley_tempcontrol/cgse_explore.py +0 -0
  20. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/keithley_tempcontrol/cgse_services.py +0 -0
  21. {keithley_tempcontrol-0.17.1 → keithley_tempcontrol-0.17.2}/src/keithley_tempcontrol/settings.yaml +0 -0
@@ -32,6 +32,11 @@ venv
32
32
 
33
33
  .idea
34
34
 
35
+ # VSCode IDE
36
+
37
+ .vscode
38
+ *.code-workspace
39
+
35
40
  # MKDOCS documentation site
36
41
 
37
42
  /site
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: keithley-tempcontrol
3
- Version: 0.17.1
3
+ Version: 0.17.2
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.1"
3
+ version = "0.17.2"
4
4
  description = "Keithley Temperature Control for CGSE"
5
5
  authors = [
6
6
  {name = "IvS KU Leuven"}
@@ -3,19 +3,28 @@ import datetime
3
3
  import re
4
4
  import socket
5
5
  import time
6
+ from functools import partial
6
7
  from typing import Annotated
7
8
 
8
9
  import typer
9
10
 
11
+ from egse.env import bool_env
10
12
  from egse.log import logging
11
13
  from egse.settings import Settings
12
14
  from egse.system import SignalCatcher
13
15
 
14
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
+
19
28
  SEPARATOR = b"\n"
20
29
  SEPARATOR_STR = SEPARATOR.decode()
21
30
 
@@ -25,8 +34,8 @@ reference_time = device_time
25
34
 
26
35
  app = typer.Typer(help="DAQ6510 Simulator")
27
36
 
28
- error_msg: str | None = None
29
- """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."""
30
39
 
31
40
 
32
41
  def create_datetime(year, month, day, hour, minute, second):
@@ -64,8 +73,14 @@ def reset():
64
73
  logger.info("RESET")
65
74
 
66
75
 
76
+ def log(level: int, msg: str):
77
+ logger.log(level, msg)
78
+
79
+
67
80
  COMMAND_ACTIONS_RESPONSES = {
68
- "*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),
69
84
  }
70
85
 
71
86
  # Check the regex at https://regex101.com
@@ -82,64 +97,104 @@ COMMAND_PATTERNS_ACTIONS_RESPONSES = {
82
97
 
83
98
  def write(conn, response: str):
84
99
  response = f"{response}{SEPARATOR_STR}".encode()
85
- logger.debug(f"write: {response = }")
100
+ if VERBOSE_DEBUG:
101
+ logger.debug(f"write: {response = }")
86
102
  conn.sendall(response)
87
103
 
88
104
 
105
+ # Keep a receive buffer per connection
106
+ _recv_buffers: dict[int, bytes] = {}
107
+
108
+
89
109
  def read(conn) -> str:
90
110
  """
91
- Reads one command string from the socket, i.e. until a linefeed ('\n') is received.
92
-
93
- Returns:
94
- 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.
95
114
  """
96
-
97
- n_total = 0
98
- buf_size = 1024 * 4
99
- command_string = bytes()
115
+ fileno = conn.fileno()
116
+ buf = _recv_buffers.get(fileno, b"")
100
117
 
101
118
  try:
102
- for _ in range(100):
103
- data = conn.recv(buf_size)
104
- n = len(data)
105
- n_total += n
106
- command_string += data
107
- # if data.endswith(b'\n'):
108
- if n < buf_size:
109
- 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
+
110
137
  except socket.timeout:
111
- # 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()
112
144
  raise
113
145
 
114
- logger.info(f"read: {command_string=}")
115
-
116
- return command_string.decode().rstrip()
117
-
118
146
 
119
- 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."""
120
149
  global COMMAND_ACTIONS_RESPONSES
121
150
  global COMMAND_PATTERNS_ACTIONS_RESPONSES
122
151
  global error_msg
123
152
 
124
- # LOGGER.debug(f"{command_string=}")
153
+ if VERBOSE_DEBUG:
154
+ logger.debug(f"{command_string=}")
125
155
 
126
156
  try:
127
157
  action, response = COMMAND_ACTIONS_RESPONSES[command_string]
128
- action and action()
129
- if error_msg:
130
- 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
131
169
  else:
132
- 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
133
173
  except KeyError:
134
174
  # try to match with a value
135
175
  for key, value in COMMAND_PATTERNS_ACTIONS_RESPONSES.items():
136
176
  if match := re.match(key, command_string, flags=re.IGNORECASE):
137
- # LOGGER.debug(f"{match=}, {match.groups()}")
177
+ if VERBOSE_DEBUG:
178
+ logger.debug(f"{match=}, {match.groups()}")
138
179
  action, response = value
139
- # LOGGER.debug(f"{action=}, {response=}")
140
- action and action(*match.groups())
141
- return error_msg or (response if isinstance(response, str) or response is None else response())
142
- 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
143
198
 
144
199
 
145
200
  def run_simulator():
@@ -152,7 +207,7 @@ def run_simulator():
152
207
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
153
208
  s.bind((HOST, DAQ_SETTINGS.PORT))
154
209
  s.listen()
155
- s.settimeout(2.0)
210
+ s.settimeout(CONNECTION_TIMEOUT)
156
211
  while True:
157
212
  while True:
158
213
  with contextlib.suppress(socket.timeout):
@@ -163,13 +218,17 @@ def run_simulator():
163
218
  with conn:
164
219
  logger.info(f"Accepted connection from {addr}")
165
220
  write(conn, "This is PLATO DAQ6510 X.X.sim")
166
- conn.settimeout(2.0)
221
+ conn.settimeout(READ_TIMEOUT)
167
222
  try:
168
223
  while True:
169
224
  error_msg = ""
170
225
  with contextlib.suppress(socket.timeout):
171
226
  data = read(conn)
172
- 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
173
232
  if data.strip() == "STOP":
174
233
  logger.info("Client requested to terminate...")
175
234
  s.close()
@@ -178,9 +237,6 @@ def run_simulator():
178
237
  response = process_command(cmd.strip())
179
238
  if response is not None:
180
239
  write(conn, response)
181
- if not data:
182
- logger.info("Client closed connection, accepting new connection...")
183
- break
184
240
  if killer.term_signal_received:
185
241
  logger.info("Terminating...")
186
242
  s.close()