cgse-common 0.17.4__tar.gz → 0.18.0__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.
- {cgse_common-0.17.4 → cgse_common-0.18.0}/.gitignore +6 -1
- {cgse_common-0.17.4 → cgse_common-0.18.0}/PKG-INFO +1 -1
- cgse_common-0.18.0/justfile +20 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/pyproject.toml +1 -1
- cgse_common-0.18.0/service_registry.db +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/device.py +1 -1
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/env.py +1 -1
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/log.py +8 -6
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/scpi.py +198 -10
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/setup.py +7 -4
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/socketdevice.py +18 -19
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/system.py +1 -1
- {cgse_common-0.17.4 → cgse_common-0.18.0}/README.md +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/noxfile.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/cgse_common/__init__.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/cgse_common/cgse.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/cgse_common/settings.yaml +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/bits.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/calibration.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/config.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/counter.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/decorators.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/dicts.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/exceptions.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/heartbeat.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/hk.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/metrics.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/observer.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/obsid.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/persistence.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/plugin.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/plugins/metrics/influxdb.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/process.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/py.typed +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/randomwalk.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/reload.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/resource.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/response.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/settings.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/settings.yaml +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/signal.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/state.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/task.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/version.py +0 -0
- {cgse_common-0.17.4 → cgse_common-0.18.0}/src/egse/zmq_ser.py +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# If you don't have 'just' installed, install it with the following command:
|
|
2
|
+
#
|
|
3
|
+
# $ uv tool install rust-just
|
|
4
|
+
#
|
|
5
|
+
# The 'just' website: https://just.systems/man/en/
|
|
6
|
+
|
|
7
|
+
default:
|
|
8
|
+
@just --list
|
|
9
|
+
|
|
10
|
+
typecheck:
|
|
11
|
+
uv run --with mypy mypy -p egse --strict
|
|
12
|
+
|
|
13
|
+
test:
|
|
14
|
+
uv run pytest -v
|
|
15
|
+
|
|
16
|
+
format:
|
|
17
|
+
uvx ruff format
|
|
18
|
+
|
|
19
|
+
check:
|
|
20
|
+
uvx ruff check --no-fix
|
|
Binary file
|
|
@@ -54,7 +54,7 @@ class DeviceControllerError(DeviceError):
|
|
|
54
54
|
super().__init__(device_name, message)
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
class DeviceConnectionError(DeviceError):
|
|
57
|
+
class DeviceConnectionError(DeviceError, ConnectionError):
|
|
58
58
|
"""A generic error for all connection type of problems.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
@@ -131,12 +131,14 @@ for handler in root_logger.handlers:
|
|
|
131
131
|
handler.addFilter(NonEGSEFilter())
|
|
132
132
|
handler.addFilter(PackageFilter())
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
# Optional: integrate with Textual logging if available
|
|
135
|
+
#
|
|
136
|
+
# try:
|
|
137
|
+
# from textual.logging import TextualHandler
|
|
138
|
+
|
|
139
|
+
# root_logger.addHandler(TextualHandler())
|
|
140
|
+
# except ImportError:
|
|
141
|
+
# pass
|
|
140
142
|
|
|
141
143
|
|
|
142
144
|
logger = egse_logger
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import re
|
|
2
3
|
import socket
|
|
3
4
|
from typing import Any
|
|
4
5
|
from typing import Dict
|
|
@@ -14,6 +15,7 @@ from egse.log import logger
|
|
|
14
15
|
|
|
15
16
|
DEFAULT_READ_TIMEOUT = 1.0 # seconds
|
|
16
17
|
DEFAULT_CONNECT_TIMEOUT = 3.0 # seconds
|
|
18
|
+
DEFAULT_READ_AFTER_CONNECT = False
|
|
17
19
|
IDENTIFICATION_QUERY = "*IDN?"
|
|
18
20
|
|
|
19
21
|
VERBOSE_DEBUG = bool_env("VERBOSE_DEBUG")
|
|
@@ -49,6 +51,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
49
51
|
settings: Optional[Dict[str, Any]] = None,
|
|
50
52
|
connect_timeout: float = DEFAULT_CONNECT_TIMEOUT,
|
|
51
53
|
read_timeout: float = DEFAULT_READ_TIMEOUT,
|
|
54
|
+
read_after_connect: bool = DEFAULT_READ_AFTER_CONNECT,
|
|
52
55
|
id_validation: Optional[str] = None,
|
|
53
56
|
):
|
|
54
57
|
"""Initialize an asynchronous Ethernet interface for SCPI communication.
|
|
@@ -60,6 +63,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
60
63
|
settings: Additional device-specific settings
|
|
61
64
|
connect_timeout: Timeout for connection attempts in seconds
|
|
62
65
|
read_timeout: Timeout for read operations in seconds
|
|
66
|
+
read_after_connect: Whether to perform a read operation immediately after connecting
|
|
63
67
|
id_validation: String that should appear in the device's identification response
|
|
64
68
|
"""
|
|
65
69
|
super().__init__()
|
|
@@ -70,6 +74,7 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
70
74
|
self.settings = settings or {}
|
|
71
75
|
self.connect_timeout = connect_timeout
|
|
72
76
|
self.read_timeout = read_timeout
|
|
77
|
+
self.read_after_connect = read_after_connect
|
|
73
78
|
self.id_validation = id_validation
|
|
74
79
|
|
|
75
80
|
self._reader = None
|
|
@@ -87,7 +92,29 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
87
92
|
def device_name(self) -> str:
|
|
88
93
|
return self._device_name
|
|
89
94
|
|
|
90
|
-
async def
|
|
95
|
+
async def get_idn_parts(self) -> list[str]:
|
|
96
|
+
"""Get the device identification string and return its parts.
|
|
97
|
+
|
|
98
|
+
The *IDN? command is sent to the device, and the response is split into its
|
|
99
|
+
components: Manufacturer, Model, Serial Number, Firmware Version.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
A list containing Manufacturer, Model, Serial Number, Firmware Version.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
DeviceError: When the device is not connected or communication fails.
|
|
106
|
+
"""
|
|
107
|
+
if not self._is_connection_open:
|
|
108
|
+
raise DeviceError(self._device_name, "Device not connected, use connect() first")
|
|
109
|
+
|
|
110
|
+
idn_str = (await self.query(IDENTIFICATION_QUERY)).decode().strip()
|
|
111
|
+
if VERBOSE_DEBUG:
|
|
112
|
+
logger.debug(f"{self._device_name} IDN response: {idn_str}")
|
|
113
|
+
return idn_str.split(",")
|
|
114
|
+
|
|
115
|
+
async def initialize(
|
|
116
|
+
self, commands: list[tuple[str, bool]] | None = None, reset_device: bool = False
|
|
117
|
+
) -> list[str | None]:
|
|
91
118
|
"""Initialize the device with optional reset and command sequence.
|
|
92
119
|
|
|
93
120
|
Performs device initialization by optionally resetting the device and then
|
|
@@ -168,10 +195,6 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
168
195
|
|
|
169
196
|
self._is_connection_open = True
|
|
170
197
|
|
|
171
|
-
response = await self.read_string()
|
|
172
|
-
if VERBOSE_DEBUG:
|
|
173
|
-
logger.debug(f"Response after connection: {response}")
|
|
174
|
-
|
|
175
198
|
logger.debug(f"Successfully connected to {self._device_name}.")
|
|
176
199
|
|
|
177
200
|
except asyncio.TimeoutError as exc:
|
|
@@ -189,6 +212,13 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
189
212
|
except OSError as exc:
|
|
190
213
|
raise DeviceConnectionError(self._device_name, f"OS error: {exc}") from exc
|
|
191
214
|
|
|
215
|
+
# Some devices require an initial read after connection.
|
|
216
|
+
# We have to read this message from the buffer and can do some version checking if needed.
|
|
217
|
+
if self.read_after_connect:
|
|
218
|
+
response = await self.read_string()
|
|
219
|
+
if VERBOSE_DEBUG:
|
|
220
|
+
logger.debug(f"Response after connection: {response}")
|
|
221
|
+
|
|
192
222
|
# Validate device identity if requested
|
|
193
223
|
if self.id_validation:
|
|
194
224
|
logger.debug("Validating connection..")
|
|
@@ -268,7 +298,8 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
268
298
|
if not command.endswith(SEPARATOR_STR):
|
|
269
299
|
command += SEPARATOR_STR
|
|
270
300
|
|
|
271
|
-
|
|
301
|
+
if VERBOSE_DEBUG:
|
|
302
|
+
logger.debug(f"-----> {command}")
|
|
272
303
|
self._writer.write(command.encode())
|
|
273
304
|
await self._writer.drain()
|
|
274
305
|
|
|
@@ -301,7 +332,8 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
301
332
|
response = await asyncio.wait_for(
|
|
302
333
|
self._reader.readuntil(separator=SEPARATOR), timeout=self.read_timeout
|
|
303
334
|
)
|
|
304
|
-
|
|
335
|
+
if VERBOSE_DEBUG:
|
|
336
|
+
logger.debug(f"<----- {response}")
|
|
305
337
|
return response
|
|
306
338
|
|
|
307
339
|
except asyncio.IncompleteReadError as exc:
|
|
@@ -341,14 +373,16 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
341
373
|
|
|
342
374
|
async with self._io_lock:
|
|
343
375
|
try:
|
|
344
|
-
if not self._is_connection_open or self._writer is None:
|
|
376
|
+
if not self._is_connection_open or self._writer is None or self._reader is None:
|
|
345
377
|
raise DeviceConnectionError(self._device_name, "Device not connected, use connect() first")
|
|
346
378
|
|
|
347
379
|
# Ensure command ends with the required terminator
|
|
348
380
|
if not command.endswith(SEPARATOR_STR):
|
|
349
381
|
command += SEPARATOR_STR
|
|
350
382
|
|
|
351
|
-
|
|
383
|
+
if VERBOSE_DEBUG:
|
|
384
|
+
logger.debug(f"-----> {command=}")
|
|
385
|
+
|
|
352
386
|
self._writer.write(command.encode())
|
|
353
387
|
await self._writer.drain()
|
|
354
388
|
|
|
@@ -360,7 +394,8 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
360
394
|
response = await asyncio.wait_for(
|
|
361
395
|
self._reader.readuntil(separator=SEPARATOR), timeout=self.read_timeout
|
|
362
396
|
)
|
|
363
|
-
|
|
397
|
+
if VERBOSE_DEBUG:
|
|
398
|
+
logger.debug(f"<----- {response=}")
|
|
364
399
|
return response
|
|
365
400
|
|
|
366
401
|
except asyncio.IncompleteReadError as exc:
|
|
@@ -392,3 +427,156 @@ class AsyncSCPIInterface(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
|
392
427
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
393
428
|
"""Async context manager exit."""
|
|
394
429
|
await self.disconnect()
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def create_channel_list(*args) -> str:
|
|
433
|
+
"""
|
|
434
|
+
Create a channel list that is understood by SCPI commands.
|
|
435
|
+
|
|
436
|
+
Channel names are device-specific.
|
|
437
|
+
|
|
438
|
+
For the DAQ6510: Channel names contain both the slot number and the channel number.
|
|
439
|
+
The slot number is the number of the slot where the card is installed at the back of
|
|
440
|
+
the device.
|
|
441
|
+
|
|
442
|
+
When addressing multiple individual channels, add each of them as a separate argument,
|
|
443
|
+
e.g. to include channels 1, 3, and 7 from slot 1, use the following command:
|
|
444
|
+
|
|
445
|
+
>>> create_channel_list(101, 103, 107)
|
|
446
|
+
'(@101, 103, 107)'
|
|
447
|
+
|
|
448
|
+
To designate a range of channels, only one argument should be given, i.e. a tuple containing
|
|
449
|
+
two channel representing the range. The following tuple `(101, 110)` will create the
|
|
450
|
+
following response: `"(@101:110)"`. The range is inclusive, so this will define a range of
|
|
451
|
+
10 channels in slot 1.
|
|
452
|
+
|
|
453
|
+
>>> create_channel_list((201, 205))
|
|
454
|
+
'(@201:205)'
|
|
455
|
+
|
|
456
|
+
See reference manual for the Keithley DAQ6510 [DAQ6510-901-01 Rev. B / September 2019],
|
|
457
|
+
chapter 11: Introduction to SCPI commands, SCPI command formatting, channel naming.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
*args: a tuple or a list of channels
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
A string containing the channel list as understood by the SCPI device.
|
|
464
|
+
|
|
465
|
+
"""
|
|
466
|
+
if not args:
|
|
467
|
+
return ""
|
|
468
|
+
|
|
469
|
+
# If only one argument is given, I expect either a tuple defining a range
|
|
470
|
+
# or just one channel. When several arguments are given, I expect them all
|
|
471
|
+
# to be individual channels.
|
|
472
|
+
|
|
473
|
+
ch_list = []
|
|
474
|
+
for arg in args:
|
|
475
|
+
if isinstance(arg, (tuple, list)):
|
|
476
|
+
match len(arg):
|
|
477
|
+
case 2:
|
|
478
|
+
ch_list.append(f"{arg[0]}:{arg[1]}")
|
|
479
|
+
case 1:
|
|
480
|
+
ch_list.append(f"{arg[0]}")
|
|
481
|
+
case _:
|
|
482
|
+
raise ValueError(
|
|
483
|
+
"Invalid argument: when providing a tuple or list, it must contain one or two elements."
|
|
484
|
+
)
|
|
485
|
+
else:
|
|
486
|
+
ch_list.append(f"{arg}")
|
|
487
|
+
|
|
488
|
+
# else:
|
|
489
|
+
# ch_list = "(@" + ",".join([str(arg) for arg in args]) + ")"
|
|
490
|
+
|
|
491
|
+
return "(@" + ",".join(x for x in ch_list if x.isdigit() or ":" in x) + ")"
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def count_number_of_channels(channel_list: str) -> int:
|
|
495
|
+
"""
|
|
496
|
+
Given a proper channel list, this function counts the number of channels.
|
|
497
|
+
For ranges, it returns the actual number of channels that are included in the range.
|
|
498
|
+
|
|
499
|
+
>>> count_number_of_channels("(@1,2,3,4,5)")
|
|
500
|
+
5
|
|
501
|
+
>>> count_number_of_channels("(@1, 3, 5)")
|
|
502
|
+
3
|
|
503
|
+
>>> count_number_of_channels("(@2:7)")
|
|
504
|
+
6
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
channel_list: a channel list as understood by the SCPI commands of DAQ6510.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
The number of channels in the list.
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
match = re.match(r"\(@(.*)\)", channel_list)
|
|
514
|
+
if match is None:
|
|
515
|
+
logger.error(f"Invalid channel specification in '{channel_list}'")
|
|
516
|
+
return 0
|
|
517
|
+
group = match.groups()[0]
|
|
518
|
+
|
|
519
|
+
parts = group.replace(" ", "").split(",")
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
count = 0
|
|
523
|
+
for part in parts:
|
|
524
|
+
if ":" in part:
|
|
525
|
+
channels = part.split(":")
|
|
526
|
+
if len(channels) != 2:
|
|
527
|
+
raise ValueError()
|
|
528
|
+
count += int(channels[1]) - int(channels[0]) + 1
|
|
529
|
+
else:
|
|
530
|
+
if not part.isdigit():
|
|
531
|
+
raise ValueError()
|
|
532
|
+
count += 1
|
|
533
|
+
except ValueError:
|
|
534
|
+
logger.error(f"Invalid channel specification in '{channel_list}'")
|
|
535
|
+
return 0
|
|
536
|
+
|
|
537
|
+
return count
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def get_channel_names(channel_list: str) -> list[str]:
|
|
541
|
+
"""
|
|
542
|
+
Generate a list of channel names from a given channel list.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
channel_list: a channel list as understood by the SCPI.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
A list of channel names.
|
|
549
|
+
"""
|
|
550
|
+
|
|
551
|
+
match = re.match(r"\(@(.*)\)", channel_list)
|
|
552
|
+
if match is None:
|
|
553
|
+
logger.error(f"Invalid channel specification in '{channel_list}'")
|
|
554
|
+
return []
|
|
555
|
+
|
|
556
|
+
group = match.groups()[0]
|
|
557
|
+
|
|
558
|
+
parts = group.replace(" ", "").split(",")
|
|
559
|
+
|
|
560
|
+
try:
|
|
561
|
+
names: list[str] = []
|
|
562
|
+
|
|
563
|
+
for part in parts:
|
|
564
|
+
if ":" in part:
|
|
565
|
+
channels = part.split(":")
|
|
566
|
+
if len(channels) != 2:
|
|
567
|
+
raise ValueError()
|
|
568
|
+
names.extend(str(ch) for ch in range(int(channels[0]), int(channels[1]) + 1))
|
|
569
|
+
else:
|
|
570
|
+
if not part.isdigit():
|
|
571
|
+
raise ValueError()
|
|
572
|
+
names.append(part)
|
|
573
|
+
|
|
574
|
+
# If there are still any invalid names, raise an error
|
|
575
|
+
if not all(True if x and x.isdigit() else False for x in names):
|
|
576
|
+
raise ValueError()
|
|
577
|
+
|
|
578
|
+
except ValueError:
|
|
579
|
+
logger.error(f"Invalid channel specification in '{channel_list}'")
|
|
580
|
+
return []
|
|
581
|
+
|
|
582
|
+
return names
|
|
@@ -936,9 +936,13 @@ class SetupManager:
|
|
|
936
936
|
def set_default_source(self, source: str):
|
|
937
937
|
self._default_source = source
|
|
938
938
|
|
|
939
|
-
def
|
|
940
|
-
|
|
939
|
+
def get_default_source(self):
|
|
940
|
+
# Trigger provider discovery because they can change the default source if they handle core-services
|
|
941
|
+
_ = self.providers
|
|
942
|
+
return self._default_source
|
|
941
943
|
|
|
944
|
+
def load_setup(self, setup_id: int = None, **kwargs):
|
|
945
|
+
source = kwargs.get("source") or self.get_default_source()
|
|
942
946
|
for provider in self.providers:
|
|
943
947
|
if provider.can_handle(source):
|
|
944
948
|
return provider.load_setup(setup_id, **kwargs)
|
|
@@ -947,7 +951,7 @@ class SetupManager:
|
|
|
947
951
|
return LocalSetupProvider().load_setup(setup_id, **kwargs)
|
|
948
952
|
|
|
949
953
|
def submit_setup(self, setup: Setup, description: str, **kwargs):
|
|
950
|
-
source = kwargs.get("source") or self.
|
|
954
|
+
source = kwargs.get("source") or self.get_default_source()
|
|
951
955
|
for provider in self.providers:
|
|
952
956
|
if provider.can_handle(source):
|
|
953
957
|
return provider.submit_setup(setup, description, **kwargs)
|
|
@@ -957,7 +961,6 @@ class SetupManager:
|
|
|
957
961
|
|
|
958
962
|
_setup_manager = SetupManager()
|
|
959
963
|
|
|
960
|
-
|
|
961
964
|
if __name__ == "__main__":
|
|
962
965
|
from egse.env import setup_env
|
|
963
966
|
|
|
@@ -45,6 +45,7 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
45
45
|
self.connect_timeout = connect_timeout
|
|
46
46
|
self.read_timeout = read_timeout
|
|
47
47
|
self.separator = separator
|
|
48
|
+
self.separator_str = separator.decode()
|
|
48
49
|
self.socket = None
|
|
49
50
|
|
|
50
51
|
@property
|
|
@@ -57,8 +58,8 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
57
58
|
Connect the device.
|
|
58
59
|
|
|
59
60
|
Raises:
|
|
60
|
-
ConnectionError: When the connection could not be established.
|
|
61
|
-
messages for more detail.
|
|
61
|
+
ConnectionError: When the connection could not be established.
|
|
62
|
+
Check the logging messages for more detail.
|
|
62
63
|
TimeoutError: When the connection timed out.
|
|
63
64
|
ValueError: When hostname or port number are not provided.
|
|
64
65
|
"""
|
|
@@ -92,9 +93,9 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
92
93
|
except TimeoutError as exc:
|
|
93
94
|
raise TimeoutError(f"{self.device_name}: Connection to {self.hostname}:{self.port} timed out.") from exc
|
|
94
95
|
except socket.gaierror as exc:
|
|
95
|
-
raise ConnectionError(f"{self.device_name}:
|
|
96
|
+
raise ConnectionError(f"{self.device_name}: Socket address info error for {self.hostname}") from exc
|
|
96
97
|
except socket.herror as exc:
|
|
97
|
-
raise ConnectionError(f"{self.device_name}:
|
|
98
|
+
raise ConnectionError(f"{self.device_name}: Socket host address error for {self.hostname}") from exc
|
|
98
99
|
except OSError as exc:
|
|
99
100
|
raise ConnectionError(f"{self.device_name}: OSError caught ({exc}).") from exc
|
|
100
101
|
|
|
@@ -108,6 +109,8 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
108
109
|
ConnectionError when the socket could not be closed.
|
|
109
110
|
"""
|
|
110
111
|
|
|
112
|
+
assert self.socket is not None # extra check + for mypy type checking
|
|
113
|
+
|
|
111
114
|
try:
|
|
112
115
|
if self.is_connection_open:
|
|
113
116
|
logger.debug(f"Disconnecting from {self.hostname}")
|
|
@@ -143,12 +146,12 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
143
146
|
If `self.read_timeout` was set to None in the constructor, this will block anyway.
|
|
144
147
|
"""
|
|
145
148
|
if not self.socket:
|
|
146
|
-
raise DeviceConnectionError(self.device_name, "
|
|
149
|
+
raise DeviceConnectionError(self.device_name, "The device is not connected, connect before reading.")
|
|
147
150
|
|
|
148
151
|
buf_size = 1024 * 4
|
|
149
152
|
response = bytearray()
|
|
150
153
|
|
|
151
|
-
# If read_timeout is None we preserve blocking
|
|
154
|
+
# If read_timeout is None we preserve blocking behavior; otherwise enforce overall timeout.
|
|
152
155
|
if self.read_timeout is None:
|
|
153
156
|
end_time = None
|
|
154
157
|
else:
|
|
@@ -211,7 +214,11 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
211
214
|
there was a socket related error.
|
|
212
215
|
"""
|
|
213
216
|
|
|
217
|
+
if not self.socket:
|
|
218
|
+
raise DeviceConnectionError(self.device_name, "The device is not connected, connect before writing.")
|
|
219
|
+
|
|
214
220
|
try:
|
|
221
|
+
command += self.separator_str if not command.endswith(self.separator_str) else ""
|
|
215
222
|
self.socket.sendall(command.encode())
|
|
216
223
|
except socket.timeout as exc:
|
|
217
224
|
raise DeviceTimeoutError(self.device_name, "Socket timeout error") from exc
|
|
@@ -237,22 +244,14 @@ class SocketDevice(DeviceConnectionInterface, DeviceTransport):
|
|
|
237
244
|
there was a socket related error.
|
|
238
245
|
"""
|
|
239
246
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
self.socket.sendall(command.encode())
|
|
244
|
-
|
|
245
|
-
# wait for, read and return the response (will be at most TBD chars)
|
|
247
|
+
if not self.socket:
|
|
248
|
+
raise DeviceConnectionError(self.device_name, "The device is not connected, connect before writing.")
|
|
246
249
|
|
|
247
|
-
|
|
250
|
+
self.write(command)
|
|
248
251
|
|
|
249
|
-
|
|
252
|
+
response = self.read()
|
|
250
253
|
|
|
251
|
-
|
|
252
|
-
raise DeviceTimeoutError(self.device_name, "Socket timeout error") from exc
|
|
253
|
-
except socket.error as exc:
|
|
254
|
-
# Interpret any socket-related error as an I/O error
|
|
255
|
-
raise DeviceConnectionError(self.device_name, "Socket communication error.") from exc
|
|
254
|
+
return response
|
|
256
255
|
|
|
257
256
|
|
|
258
257
|
class AsyncSocketDevice(AsyncDeviceInterface, AsyncDeviceTransport):
|
|
@@ -379,7 +379,7 @@ def ignore_m_warning(modules=None):
|
|
|
379
379
|
pass
|
|
380
380
|
|
|
381
381
|
|
|
382
|
-
def now(utc: bool = True):
|
|
382
|
+
def now(utc: bool = True) -> datetime.datetime:
|
|
383
383
|
"""Returns a datetime object for the current time in UTC or local time."""
|
|
384
384
|
if utc:
|
|
385
385
|
return datetime.datetime.now(tz=datetime.timezone.utc)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|