cgse-common 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 (48) hide show
  1. {cgse_common-0.17.2 → cgse_common-0.17.3}/PKG-INFO +1 -1
  2. {cgse_common-0.17.2 → cgse_common-0.17.3}/pyproject.toml +1 -1
  3. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/device.py +0 -70
  4. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/scpi.py +52 -123
  5. cgse_common-0.17.2/src/egse/plugins/metrics/duckdb.py +0 -442
  6. cgse_common-0.17.2/src/egse/plugins/metrics/timescaledb.py +0 -596
  7. cgse_common-0.17.2/src/egse/ratelimit.py +0 -275
  8. cgse_common-0.17.2/src/egse/socketdevice.py +0 -379
  9. {cgse_common-0.17.2 → cgse_common-0.17.3}/.gitignore +0 -0
  10. {cgse_common-0.17.2 → cgse_common-0.17.3}/README.md +0 -0
  11. {cgse_common-0.17.2 → cgse_common-0.17.3}/justfile +0 -0
  12. {cgse_common-0.17.2 → cgse_common-0.17.3}/noxfile.py +0 -0
  13. {cgse_common-0.17.2 → cgse_common-0.17.3}/service_registry.db +0 -0
  14. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/cgse_common/__init__.py +0 -0
  15. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/cgse_common/cgse.py +0 -0
  16. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/cgse_common/settings.yaml +0 -0
  17. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/bits.py +0 -0
  18. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/calibration.py +0 -0
  19. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/config.py +0 -0
  20. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/counter.py +0 -0
  21. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/decorators.py +0 -0
  22. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/dicts.py +0 -0
  23. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/env.py +0 -0
  24. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/exceptions.py +0 -0
  25. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/heartbeat.py +0 -0
  26. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/hk.py +0 -0
  27. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/log.py +0 -0
  28. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/metrics.py +0 -0
  29. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/observer.py +0 -0
  30. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/obsid.py +0 -0
  31. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/persistence.py +0 -0
  32. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/plugin.py +0 -0
  33. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/plugins/metrics/influxdb.py +0 -0
  34. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/process.py +0 -0
  35. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/py.typed +0 -0
  36. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/randomwalk.py +0 -0
  37. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/reload.py +0 -0
  38. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/resource.py +0 -0
  39. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/response.py +0 -0
  40. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/settings.py +0 -0
  41. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/settings.yaml +0 -0
  42. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/setup.py +0 -0
  43. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/signal.py +0 -0
  44. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/state.py +0 -0
  45. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/system.py +0 -0
  46. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/task.py +0 -0
  47. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/version.py +0 -0
  48. {cgse_common-0.17.2 → cgse_common-0.17.3}/src/egse/zmq_ser.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cgse-common
3
- Version: 0.17.2
3
+ Version: 0.17.3
4
4
  Summary: Software framework to support hardware testing
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 = "cgse-common"
3
- version = "0.17.2"
3
+ version = "0.17.3"
4
4
  description = "Software framework to support hardware testing"
5
5
  authors = [
6
6
  {name = "IvS KU Leuven"}
@@ -339,76 +339,6 @@ class AsyncDeviceTransport:
339
339
  return await self.trans(command)
340
340
 
341
341
 
342
- class AsyncDeviceConnectionInterface(DeviceConnectionObservable):
343
- """Generic connection interface for all Device classes and Controllers.
344
-
345
- This interface shall be implemented in the Controllers that directly connect to the
346
- hardware, but also in the simulators to guarantee an identical interface as the controllers.
347
-
348
- This interface will be implemented in the Proxy classes through the
349
- YAML definitions. Therefore, the YAML files shall define at least
350
- the following commands: `connect`, `disconnect`, `reconnect`, `is_connected`.
351
- """
352
-
353
- def __init__(self):
354
- super().__init__()
355
-
356
- def __enter__(self):
357
- self.connect()
358
- return self
359
-
360
- def __exit__(self, exc_type, exc_val, exc_tb):
361
- self.disconnect()
362
-
363
- async def connect(self) -> None:
364
- """Connect to the device controller.
365
-
366
- Raises:
367
- ConnectionError: when the connection can not be opened.
368
- """
369
-
370
- raise NotImplementedError
371
-
372
- async def disconnect(self) -> None:
373
- """Disconnect from the device controller.
374
-
375
- Raises:
376
- ConnectionError: when the connection can not be closed.
377
- """
378
- raise NotImplementedError
379
-
380
- async def reconnect(self):
381
- """Reconnect the device controller.
382
-
383
- Raises:
384
- ConnectionError: when the device can not be reconnected for some reason.
385
- """
386
- raise NotImplementedError
387
-
388
- async def is_connected(self) -> bool:
389
- """Check if the device is connected.
390
-
391
- Returns:
392
- True if the device is connected and responds to a command, False otherwise.
393
- """
394
- raise NotImplementedError
395
-
396
-
397
- class AsyncDeviceInterface(AsyncDeviceConnectionInterface):
398
- """A generic interface for all device classes."""
399
-
400
- def is_simulator(self) -> bool:
401
- """Checks whether the device is a simulator rather than a real hardware controller.
402
-
403
- This can be useful for testing purposes or when doing actual movement simulations.
404
-
405
- Returns:
406
- True if the Device is a Simulator; False if the Device is connected to real hardware.
407
- """
408
-
409
- raise NotImplementedError
410
-
411
-
412
342
  class DeviceFactoryInterface:
413
343
  """
414
344
  Base class for creating a device factory class to access devices.
@@ -4,23 +4,17 @@ from typing import Any
4
4
  from typing import Dict
5
5
  from typing import Optional
6
6
 
7
- from egse.device import AsyncDeviceInterface
8
7
  from egse.device import AsyncDeviceTransport
9
8
  from egse.device import DeviceConnectionError
10
9
  from egse.device import DeviceError
11
10
  from egse.device import DeviceTimeoutError
12
- from egse.env import bool_env
13
11
  from egse.log import logger
14
12
 
13
+ # Constants that can be overridden by specific device implementations
15
14
  DEFAULT_READ_TIMEOUT = 1.0 # seconds
16
15
  DEFAULT_CONNECT_TIMEOUT = 3.0 # seconds
17
16
  IDENTIFICATION_QUERY = "*IDN?"
18
17
 
19
- VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG")
20
-
21
- SEPARATOR = b"\n"
22
- SEPARATOR_STR = SEPARATOR.decode()
23
-
24
18
 
25
19
  class SCPICommand:
26
20
  """Base class for SCPI commands."""
@@ -38,7 +32,7 @@ class SCPICommand:
38
32
  raise NotImplementedError("Subclasses must implement get_cmd_string().")
39
33
 
40
34
 
41
- class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
35
+ class AsyncSCPIInterface(AsyncDeviceTransport):
42
36
  """Generic asynchronous interface for devices that use SCPI commands over Ethernet."""
43
37
 
44
38
  def __init__(
@@ -62,9 +56,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
62
56
  read_timeout: Timeout for read operations in seconds
63
57
  id_validation: String that should appear in the device's identification response
64
58
  """
65
- super().__init__()
66
-
67
- self._device_name = device_name
59
+ self.device_name = device_name
68
60
  self.hostname = hostname
69
61
  self.port = port
70
62
  self.settings = settings or {}
@@ -78,66 +70,8 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
78
70
  self._connect_lock = asyncio.Lock()
79
71
  """Prevents multiple, simultaneous connect or disconnect attempts."""
80
72
  self._io_lock = asyncio.Lock()
81
- """Prevents multiple coroutines from attempting to read, write or query from the same stream at the same time."""
82
-
83
- def is_simulator(self) -> bool:
84
- return False
85
-
86
- @property
87
- def device_name(self) -> str:
88
- return self._device_name
89
-
90
- async def initialize(self, commands: list[tuple[str, bool]] = None, reset_device: bool = False) -> list[str | None]:
91
- """Initialize the device with optional reset and command sequence.
92
-
93
- Performs device initialization by optionally resetting the device and then
94
- executing a sequence of commands. Each command can optionally expect a
95
- response that will be logged for debugging purposes.
96
-
97
- Args:
98
- commands: List of tuples containing (command_string, expects_response).
99
- Each tuple specifies a command to send and whether to wait for and
100
- log the response. Defaults to None (no commands executed).
101
- reset_device: Whether to send a reset command (*RST) before executing
102
- the command sequence. Defaults to False.
103
-
104
- Returns:
105
- Response for each of the commands, or None when no response was expected.
106
-
107
- Raises:
108
- Any exceptions raised by the underlying write() or trans() methods,
109
- typically communication errors or device timeouts.
110
-
111
- Example:
112
- await device.initialize(
113
- [
114
- ("*IDN?", True), # Query device ID, expect response
115
- ("SYST:ERR?", True), # Check for errors, expect response
116
- ("OUTP ON", False), # Enable output, no response expected
117
- ],
118
- reset_device=True
119
- )
120
- """
121
-
122
- commands = commands or []
123
- responses = []
124
-
125
- if reset_device:
126
- logger.info(f"Resetting the {self._device_name}...")
127
- await self.write("*RST") # this also resets the user-defined buffer
128
-
129
- for cmd, expects_response in commands:
130
- if expects_response:
131
- logger.debug(f"Sending {cmd}...")
132
- response = (await self.trans(cmd)).decode().strip()
133
- responses.append(response)
134
- logger.debug(f"{response = }")
135
- else:
136
- logger.debug(f"Sending {cmd}...")
137
- await self.write(cmd)
138
- responses.append(None)
139
-
140
- return responses
73
+ """Prevents multiple coroutines from attempting to read, write or query from the same stream
74
+ at the same time."""
141
75
 
142
76
  async def connect(self) -> None:
143
77
  """Connect to the device asynchronously.
@@ -150,51 +84,47 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
150
84
  async with self._connect_lock:
151
85
  # Sanity checks
152
86
  if self._is_connection_open:
153
- logger.warning(f"{self._device_name}: Trying to connect to an already connected device.")
87
+ logger.warning(f"{self.device_name}: Trying to connect to an already connected device.")
154
88
  return
155
89
 
156
90
  if not self.hostname:
157
- raise ValueError(f"{self._device_name}: Hostname is not initialized.")
91
+ raise ValueError(f"{self.device_name}: Hostname is not initialized.")
158
92
 
159
93
  if not self.port:
160
- raise ValueError(f"{self._device_name}: Port number is not initialized.")
94
+ raise ValueError(f"{self.device_name}: Port number is not initialized.")
161
95
 
162
96
  # Attempt to establish a connection
163
97
  try:
164
- logger.debug(f'Connecting to {self._device_name} at "{self.hostname}" using port {self.port}')
98
+ logger.debug(f'Connecting to {self.device_name} at "{self.hostname}" using port {self.port}')
165
99
 
166
100
  connect_task = asyncio.open_connection(self.hostname, self.port)
167
101
  self._reader, self._writer = await asyncio.wait_for(connect_task, timeout=self.connect_timeout)
168
102
 
169
103
  self._is_connection_open = True
170
104
 
171
- response = await self.read_string()
172
- if VERBOSE_DEBUG:
173
- logger.debug(f"Response after connection: {response}")
174
-
175
- logger.debug(f"Successfully connected to {self._device_name}.")
105
+ logger.debug(f"Successfully connected to {self.device_name}.")
176
106
 
177
107
  except asyncio.TimeoutError as exc:
178
108
  raise DeviceTimeoutError(
179
- self._device_name, f"Connection to {self.hostname}:{self.port} timed out"
109
+ self.device_name, f"Connection to {self.hostname}:{self.port} timed out"
180
110
  ) from exc
181
111
  except ConnectionRefusedError as exc:
182
112
  raise DeviceConnectionError(
183
- self._device_name, f"Connection refused to {self.hostname}:{self.port}"
113
+ self.device_name, f"Connection refused to {self.hostname}:{self.port}"
184
114
  ) from exc
185
115
  except socket.gaierror as exc:
186
- raise DeviceConnectionError(self._device_name, f"Address resolution error for {self.hostname}") from exc
116
+ raise DeviceConnectionError(self.device_name, f"Address resolution error for {self.hostname}") from exc
187
117
  except socket.herror as exc:
188
- raise DeviceConnectionError(self._device_name, f"Host address error for {self.hostname}") from exc
118
+ raise DeviceConnectionError(self.device_name, f"Host address error for {self.hostname}") from exc
189
119
  except OSError as exc:
190
- raise DeviceConnectionError(self._device_name, f"OS error: {exc}") from exc
120
+ raise DeviceConnectionError(self.device_name, f"OS error: {exc}") from exc
191
121
 
192
122
  # Validate device identity if requested
193
123
  if self.id_validation:
194
124
  logger.debug("Validating connection..")
195
125
  if not await self.is_connected():
196
126
  await self.disconnect()
197
- raise DeviceConnectionError(self._device_name, "Device connected but failed identity verification")
127
+ raise DeviceConnectionError(self.device_name, "Device connected but failed identity verification")
198
128
 
199
129
  async def disconnect(self) -> None:
200
130
  """Disconnect from the device asynchronously.
@@ -205,20 +135,22 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
205
135
  async with self._connect_lock:
206
136
  try:
207
137
  if self._is_connection_open and self._writer is not None:
208
- logger.debug(f"Disconnecting from {self._device_name} at {self.hostname}")
138
+ logger.debug(f"Disconnecting from {self.device_name} at {self.hostname}")
209
139
  self._writer.close()
210
140
  await self._writer.wait_closed()
211
141
  self._writer = None
212
142
  self._reader = None
213
143
  self._is_connection_open = False
214
144
  except Exception as exc:
215
- raise DeviceConnectionError(self._device_name, f"Could not close connection: {exc}") from exc
145
+ raise DeviceConnectionError(self.device_name, f"Could not close connection: {exc}") from exc
216
146
 
217
147
  async def reconnect(self) -> None:
218
148
  """Reconnect to the device asynchronously."""
219
- await self.disconnect()
220
- await asyncio.sleep(0.1)
221
- await self.connect()
149
+ async with self._connect_lock:
150
+ if self._is_connection_open:
151
+ await self.disconnect()
152
+ await asyncio.sleep(0.1)
153
+ await self.connect()
222
154
 
223
155
  async def is_connected(self) -> bool:
224
156
  """Check if the device is connected and responds correctly to identification.
@@ -236,7 +168,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
236
168
  # Validate the response if validation string is provided
237
169
  if self.id_validation and self.id_validation not in id_response:
238
170
  logger.error(
239
- f"{self._device_name}: Device did not respond correctly to identification query. "
171
+ f"{self.device_name}: Device did not respond correctly to identification query. "
240
172
  f'Expected "{self.id_validation}" in response, got: {id_response}'
241
173
  )
242
174
  await self.disconnect()
@@ -245,7 +177,8 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
245
177
  return True
246
178
 
247
179
  except DeviceError as exc:
248
- logger.error(f"{self._device_name}: Connection test failed: {exc}", exc_info=True)
180
+ logger.exception(exc)
181
+ logger.error(f"{self.device_name}: Connection test failed")
249
182
  await self.disconnect()
250
183
  return False
251
184
 
@@ -262,20 +195,19 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
262
195
  async with self._io_lock:
263
196
  try:
264
197
  if not self._is_connection_open or self._writer is None:
265
- raise DeviceConnectionError(self._device_name, "Device not connected, use connect() first")
198
+ raise DeviceConnectionError(self.device_name, "Device not connected, use connect() first")
266
199
 
267
- # Ensure command ends with the proper separator or terminator
268
- if not command.endswith(SEPARATOR_STR):
269
- command += SEPARATOR_STR
200
+ # Ensure command ends with newline
201
+ if not command.endswith("\n"):
202
+ command += "\n"
270
203
 
271
- logger.info(f"-----> {command}")
272
204
  self._writer.write(command.encode())
273
205
  await self._writer.drain()
274
206
 
275
207
  except asyncio.TimeoutError as exc:
276
- raise DeviceTimeoutError(self._device_name, "Write operation timed out") from exc
208
+ raise DeviceTimeoutError(self.device_name, "Write operation timed out") from exc
277
209
  except (ConnectionError, OSError) as exc:
278
- raise DeviceConnectionError(self._device_name, f"Communication error: {exc}") from exc
210
+ raise DeviceConnectionError(self.device_name, f"Communication error: {exc}") from exc
279
211
 
280
212
  async def read(self) -> bytes:
281
213
  """
@@ -290,7 +222,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
290
222
  """
291
223
  async with self._io_lock:
292
224
  if not self._is_connection_open or self._reader is None:
293
- raise DeviceConnectionError(self._device_name, "Device not connected, use connect() first")
225
+ raise DeviceConnectionError(self.device_name, "Device not connected, use connect() first")
294
226
 
295
227
  try:
296
228
  # First, small delay to allow device to prepare response
@@ -299,19 +231,18 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
299
231
  # Try to read until newline (common SCPI terminator)
300
232
  try:
301
233
  response = await asyncio.wait_for(
302
- self._reader.readuntil(separator=SEPARATOR), timeout=self.read_timeout
234
+ self._reader.readuntil(separator=b"\n"), timeout=self.read_timeout
303
235
  )
304
- logger.info(f"<----- {response}")
305
236
  return response
306
237
 
307
238
  except asyncio.IncompleteReadError as exc:
308
239
  # Connection closed before receiving full response
309
- logger.warning(f"{self._device_name}: Incomplete read, got {len(exc.partial)} bytes")
310
- return exc.partial if exc.partial else SEPARATOR
240
+ logger.warning(f"{self.device_name}: Incomplete read, got {len(exc.partial)} bytes")
241
+ return exc.partial if exc.partial else b"\r\n"
311
242
 
312
243
  except asyncio.LimitOverrunError:
313
244
  # Response too large for buffer
314
- logger.warning(f"{self._device_name}: Response exceeded buffer limits")
245
+ logger.warning(f"{self.device_name}: Response exceeded buffer limits")
315
246
  # Fall back to reading a large chunk
316
247
  return await asyncio.wait_for(
317
248
  self._reader.read(8192), # Larger buffer for exceptional cases
@@ -319,9 +250,9 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
319
250
  )
320
251
 
321
252
  except asyncio.TimeoutError as exc:
322
- raise DeviceTimeoutError(self._device_name, "Read operation timed out") from exc
253
+ raise DeviceTimeoutError(self.device_name, "Read operation timed out") from exc
323
254
  except Exception as exc:
324
- raise DeviceConnectionError(self._device_name, f"Read error: {exc}") from exc
255
+ raise DeviceConnectionError(self.device_name, f"Read error: {exc}") from exc
325
256
 
326
257
  async def trans(self, command: str) -> bytes:
327
258
  """
@@ -342,35 +273,33 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
342
273
  async with self._io_lock:
343
274
  try:
344
275
  if not self._is_connection_open or self._writer is None:
345
- raise DeviceConnectionError(self._device_name, "Device not connected, use connect() first")
276
+ raise DeviceConnectionError(self.device_name, "Device not connected, use connect() first")
346
277
 
347
- # Ensure command ends with the required terminator
348
- if not command.endswith(SEPARATOR_STR):
349
- command += SEPARATOR_STR
278
+ # Ensure command ends with newline
279
+ if not command.endswith("\n"):
280
+ command += "\n"
350
281
 
351
- logger.info(f"-----> {command=}")
352
282
  self._writer.write(command.encode())
353
283
  await self._writer.drain()
354
284
 
355
- # First, small delay to allow the device to prepare a response
285
+ # First, small delay to allow device to prepare response
356
286
  await asyncio.sleep(0.01)
357
287
 
358
- # Try to read until the required separator (common SCPI terminator)
288
+ # Try to read until newline (common SCPI terminator)
359
289
  try:
360
290
  response = await asyncio.wait_for(
361
- self._reader.readuntil(separator=SEPARATOR), timeout=self.read_timeout
291
+ self._reader.readuntil(separator=b"\n"), timeout=self.read_timeout
362
292
  )
363
- logger.info(f"<----- {response=}")
364
293
  return response
365
294
 
366
295
  except asyncio.IncompleteReadError as exc:
367
296
  # Connection closed before receiving full response
368
- logger.warning(f"{self._device_name}: Incomplete read, got {len(exc.partial)} bytes")
369
- return exc.partial if exc.partial else SEPARATOR
297
+ logger.warning(f"{self.device_name}: Incomplete read, got {len(exc.partial)} bytes")
298
+ return exc.partial if exc.partial else b"\r\n"
370
299
 
371
300
  except asyncio.LimitOverrunError:
372
301
  # Response too large for buffer
373
- logger.warning(f"{self._device_name}: Response exceeded buffer limits")
302
+ logger.warning(f"{self.device_name}: Response exceeded buffer limits")
374
303
  # Fall back to reading a large chunk
375
304
  return await asyncio.wait_for(
376
305
  self._reader.read(8192), # Larger buffer for exceptional cases
@@ -378,11 +307,11 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
378
307
  )
379
308
 
380
309
  except asyncio.TimeoutError as exc:
381
- raise DeviceTimeoutError(self._device_name, "Communication timed out") from exc
310
+ raise DeviceTimeoutError(self.device_name, "Communication timed out") from exc
382
311
  except (ConnectionError, OSError) as exc:
383
- raise DeviceConnectionError(self._device_name, f"Communication error: {exc}") from exc
312
+ raise DeviceConnectionError(self.device_name, f"Communication error: {exc}") from exc
384
313
  except Exception as exc:
385
- raise DeviceConnectionError(self._device_name, f"Transaction error: {exc}") from exc
314
+ raise DeviceConnectionError(self.device_name, f"Transaction error: {exc}") from exc
386
315
 
387
316
  async def __aenter__(self):
388
317
  """Async context manager entry."""