cgse-common 2023.1.4__py3-none-any.whl → 2024.1.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.
- {cgse_common-2023.1.4.dist-info → cgse_common-2024.1.4.dist-info}/METADATA +28 -25
- cgse_common-2024.1.4.dist-info/RECORD +36 -0
- {cgse_common-2023.1.4.dist-info → cgse_common-2024.1.4.dist-info}/WHEEL +1 -1
- cgse_common-2024.1.4.dist-info/entry_points.txt +2 -0
- egse/bits.py +266 -41
- egse/calibration.py +250 -0
- egse/command.py +10 -29
- egse/config.py +17 -12
- egse/control.py +0 -81
- egse/decorators.py +8 -8
- egse/device.py +3 -1
- egse/env.py +411 -106
- egse/hk.py +794 -0
- egse/metrics.py +106 -0
- egse/monitoring.py +1 -0
- egse/resource.py +70 -2
- egse/response.py +101 -0
- egse/settings.py +33 -31
- egse/settings.yaml +0 -973
- egse/setup.py +116 -81
- egse/system.py +33 -14
- cgse_common-2023.1.4.dist-info/RECORD +0 -32
- cgse_common-2023.1.4.dist-info/entry_points.txt +0 -3
egse/calibration.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from egse.setup import NavigableDict, Setup, SetupError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def apply_gain_offset(counts: float, gain: float, offset: float) -> float:
|
|
7
|
+
""" Applies the given gain and offset to the given counts.
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
counts: Uncalibrated, raw data [ADU]
|
|
11
|
+
gain: Gain to apply
|
|
12
|
+
offset: Offset to apply
|
|
13
|
+
|
|
14
|
+
Returns: Counts after applying the given gain and offset.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
return counts * gain + offset
|
|
18
|
+
|
|
19
|
+
#########################
|
|
20
|
+
# Temperature calibration
|
|
21
|
+
#########################
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def counts_to_temperature(sensor_name: str, counts: float, sensor_info: NavigableDict, setup: Setup):
|
|
25
|
+
""" Converts the given counts for the given sensor to temperature.
|
|
26
|
+
|
|
27
|
+
This conversion can be done as follows:
|
|
28
|
+
|
|
29
|
+
- (1) Directly from counts to temperature, by applying the gain and offset;
|
|
30
|
+
- (2) Directly from counts to temperature, by applying a function;
|
|
31
|
+
- (3) From counts, via resistance, to temperature.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
sensor_name: Sensor name
|
|
35
|
+
counts: Uncalibrated, raw data [ADU]
|
|
36
|
+
sensor_info: Calibration information for the given sensor (type)
|
|
37
|
+
setup: Setup
|
|
38
|
+
|
|
39
|
+
Returns: Calibrated temperature [°C] for the given sensor
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# (1) Conversion: temperature = counts * gain + offset
|
|
43
|
+
|
|
44
|
+
if "counts_to_temperature_gain" in sensor_info and "counts_to_temperature_offset" in sensor_info:
|
|
45
|
+
|
|
46
|
+
return apply_gain_offset(counts,
|
|
47
|
+
gain=eval(str(sensor_info.counts_to_temperature_gain)),
|
|
48
|
+
offset=sensor_info.counts_to_temperature_offset)
|
|
49
|
+
|
|
50
|
+
# (2) Conversion: temperature = func(counts)
|
|
51
|
+
|
|
52
|
+
if "counts_to_temperature" in sensor_info:
|
|
53
|
+
|
|
54
|
+
# (2a) Polynomial
|
|
55
|
+
|
|
56
|
+
if sensor_info.counts_to_temperature.method == "polynomial":
|
|
57
|
+
return np.polyval(sensor_info.counts_to_temperature.counts_to_temperature_coefficients, counts)
|
|
58
|
+
|
|
59
|
+
# (3) Conversion: counts -> resistance -> temperature
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
resistance = counts_to_resistance(sensor_name, counts, sensor_info)
|
|
63
|
+
return resistance_to_temperature(sensor_name, resistance, sensor_info, setup)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def counts_to_resistance(sensor_name: str, counts: float, sensor_info: NavigableDict):
|
|
67
|
+
""" Converts the given counts for the given sensor to resistance.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
sensor_name: Sensor name
|
|
71
|
+
counts: Uncalibrated, raw data [ADU]
|
|
72
|
+
sensor_info: Calibration information for the given sensor (type)
|
|
73
|
+
|
|
74
|
+
Returns: Resistance [Ohm] for the given sensor.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Offset (if any)
|
|
78
|
+
|
|
79
|
+
counts_to_resistance_offset = sensor_info.counts_to_resistance_offset \
|
|
80
|
+
if "counts_to_resistance_offset" in sensor_info \
|
|
81
|
+
else 0
|
|
82
|
+
|
|
83
|
+
# Conversion: counts -> voltage -> resistance
|
|
84
|
+
|
|
85
|
+
if "counts_to_voltage_gain" in sensor_info and "voltage_to_resistance_gain" in sensor_info:
|
|
86
|
+
|
|
87
|
+
return apply_gain_offset(counts,
|
|
88
|
+
gain=sensor_info.counts_to_voltage_gain * sensor_info.voltage_to_resistance_gain,
|
|
89
|
+
offset=counts_to_resistance_offset)
|
|
90
|
+
|
|
91
|
+
# Conversion: counts -> resistance
|
|
92
|
+
|
|
93
|
+
elif "counts_to_resistance_gain" in sensor_info:
|
|
94
|
+
|
|
95
|
+
return apply_gain_offset(counts,
|
|
96
|
+
gain=sensor_info.counts_to_resistance_gain,
|
|
97
|
+
offset=counts_to_resistance_offset)
|
|
98
|
+
|
|
99
|
+
raise SetupError(f"Setup does not contain info for conversion from counts to resistance for {sensor_name}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def resistance_to_temperature(sensor_name: str, resistance: float, sensor_info: NavigableDict, setup: Setup):
|
|
103
|
+
""" Converts the given resistance for the given sensor to temperature.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
sensor_name: Sensor name
|
|
107
|
+
resistance: Resistance [Ohm]
|
|
108
|
+
sensor_info: Calibration information for the given sensor (type)
|
|
109
|
+
setup: Setup
|
|
110
|
+
|
|
111
|
+
Returns: Temperature [°C] for the given sensor.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
resistance_to_temperature_info = sensor_info.resistance_to_temperature
|
|
115
|
+
|
|
116
|
+
# Series resistance (if any)
|
|
117
|
+
|
|
118
|
+
if "series_resistance" in resistance_to_temperature_info:
|
|
119
|
+
|
|
120
|
+
series_resistance = resistance_to_temperature_info.series_resistance
|
|
121
|
+
if sensor_name in resistance_to_temperature_info:
|
|
122
|
+
series_resistance = series_resistance[sensor_name]
|
|
123
|
+
resistance -= series_resistance
|
|
124
|
+
|
|
125
|
+
method: str = resistance_to_temperature_info.method
|
|
126
|
+
|
|
127
|
+
if "divide_resistance_by" in resistance_to_temperature_info:
|
|
128
|
+
resistance /= resistance_to_temperature_info.divide_resistance_by
|
|
129
|
+
|
|
130
|
+
# Polynomial
|
|
131
|
+
|
|
132
|
+
if method == "polynomial":
|
|
133
|
+
|
|
134
|
+
# Coefficients given for conversion temperature -> resistance
|
|
135
|
+
|
|
136
|
+
if "temperature_to_resistance_coefficients" in resistance_to_temperature_info:
|
|
137
|
+
return solve_temperature(resistance_to_temperature_info.temperature_to_resistance_coefficients, resistance)
|
|
138
|
+
|
|
139
|
+
# Coefficients given for conversion resistance -> temperature
|
|
140
|
+
|
|
141
|
+
if "resistance_to_temperature_coefficients" in resistance_to_temperature_info:
|
|
142
|
+
return np.polyval(resistance_to_temperature_info.resistance_to_temperature_coefficients, resistance)
|
|
143
|
+
|
|
144
|
+
elif method == "callendar_van_dusen":
|
|
145
|
+
|
|
146
|
+
standard = resistance_to_temperature_info.standard
|
|
147
|
+
ref_resistance = resistance_to_temperature_info.ref_resistance
|
|
148
|
+
|
|
149
|
+
return callendar_van_dusen(resistance, ref_resistance, standard, setup)
|
|
150
|
+
|
|
151
|
+
else:
|
|
152
|
+
raise SetupError(f"Setup does not contain info for conversion from resistance to temperature for {sensor_name}")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def solve_temperature(temperature_to_resistance_coefficients, resistance: float):
|
|
156
|
+
""" Solves the temperature from the temperature -> resistance polynomial.
|
|
157
|
+
|
|
158
|
+
For the given temperature -> resistance polynomial and the given resistance, we determine what the corresponding
|
|
159
|
+
temperature is by:
|
|
160
|
+
|
|
161
|
+
- Finding the roots of polynomial(temperature) = resistance;
|
|
162
|
+
- Discarding the roots with an imaginary component;
|
|
163
|
+
- Selecting the remaining root in the relevant temperature regime (here: [-200°C, 200°C]).
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
temperature_to_resistance_poly = np.poly1d(temperature_to_resistance_coefficients)
|
|
167
|
+
temperatures = (temperature_to_resistance_poly - resistance).roots
|
|
168
|
+
|
|
169
|
+
for temperature in temperatures:
|
|
170
|
+
if temperature.imag == 0 and -200 <= temperature <= 200:
|
|
171
|
+
return temperature.real
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def callendar_van_dusen(resistance: float, ref_resistance: float, standard: str, setup: Setup):
|
|
175
|
+
""" Solves the Callendar - van Dusen equation for temperature.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
resistance: Resistance [Ohm] for which to calculate the temperature
|
|
179
|
+
ref_resistance: Resistance [Ohm] for a temperature of 0°C
|
|
180
|
+
standard: Sensor standard
|
|
181
|
+
setup: Setup
|
|
182
|
+
|
|
183
|
+
Returns: Temperature [°C] corresponding to the given resistance.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
# Resistances higher than the reference resistance correspond to
|
|
187
|
+
|
|
188
|
+
coefficients = setup.sensor_calibration.callendar_van_dusen[standard]
|
|
189
|
+
|
|
190
|
+
# Positive temperatures
|
|
191
|
+
|
|
192
|
+
if resistance >= ref_resistance:
|
|
193
|
+
resistance_to_temperature_coefficients = [ref_resistance * coefficients.C,
|
|
194
|
+
-ref_resistance * 100 * coefficients.C,
|
|
195
|
+
ref_resistance * coefficients.B,
|
|
196
|
+
ref_resistance * coefficients.A, ref_resistance * 1]
|
|
197
|
+
|
|
198
|
+
# Negative temperatures
|
|
199
|
+
|
|
200
|
+
else:
|
|
201
|
+
resistance_to_temperature_coefficients = [ref_resistance * coefficients.B,
|
|
202
|
+
ref_resistance * coefficients.A,
|
|
203
|
+
ref_resistance * 1]
|
|
204
|
+
|
|
205
|
+
return solve_temperature(resistance_to_temperature_coefficients, resistance)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def chebychev(resistance: float, sensor_info: NavigableDict):
|
|
209
|
+
""" Solves the Chebychev equation for temperature.
|
|
210
|
+
|
|
211
|
+
Implemented as specified in the calibration certificate of the LakeShore Cernox sensors.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
resistance:Resistance [Ohm] for which to calculate the temperature
|
|
215
|
+
sensor_info: Calibration information
|
|
216
|
+
|
|
217
|
+
Returns: Temperature [°C] corresponding to the given resistance.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
num_fit_ranges = sensor_info.num_fit_ranges
|
|
221
|
+
|
|
222
|
+
for fit_range_index in range(1, num_fit_ranges + 1):
|
|
223
|
+
|
|
224
|
+
range_info = sensor_info[f"range{fit_range_index}"]
|
|
225
|
+
|
|
226
|
+
resistance_lower_limit, resistance_upper_limit = range_info.resistance_range
|
|
227
|
+
|
|
228
|
+
if resistance_lower_limit <= resistance <= resistance_upper_limit:
|
|
229
|
+
|
|
230
|
+
if range_info.fit_type == "LOG":
|
|
231
|
+
z = np.log10(resistance)
|
|
232
|
+
|
|
233
|
+
zl, zu = range_info.z_range
|
|
234
|
+
order = range_info.order
|
|
235
|
+
coefficients = range_info.coefficients
|
|
236
|
+
|
|
237
|
+
temperature = 0
|
|
238
|
+
|
|
239
|
+
for index in range(0, order + 1):
|
|
240
|
+
|
|
241
|
+
k = ((z-zl)-(zu-z))/(zu-zl)
|
|
242
|
+
temperature += coefficients[index] * np.cos(index * np.arccos(k))
|
|
243
|
+
|
|
244
|
+
return temperature
|
|
245
|
+
|
|
246
|
+
############################
|
|
247
|
+
# Supply voltage calibration
|
|
248
|
+
############################
|
|
249
|
+
|
|
250
|
+
# TODO
|
egse/command.py
CHANGED
|
@@ -87,6 +87,7 @@ Do we need additional hooks into this commanding?
|
|
|
87
87
|
* provide an execute method for the CommandExecution that executes the command
|
|
88
88
|
with the saved parameters
|
|
89
89
|
"""
|
|
90
|
+
|
|
90
91
|
import functools
|
|
91
92
|
import inspect
|
|
92
93
|
import logging
|
|
@@ -149,7 +150,6 @@ def dry_run(func: Callable) -> Callable:
|
|
|
149
150
|
|
|
150
151
|
@functools.wraps(func)
|
|
151
152
|
def func_wrapper(self, *args, **kwargs):
|
|
152
|
-
|
|
153
153
|
from egse.state import GlobalState # prevent circular import
|
|
154
154
|
|
|
155
155
|
if GlobalState.dry_run:
|
|
@@ -167,9 +167,7 @@ def dry_run(func: Callable) -> Callable:
|
|
|
167
167
|
else:
|
|
168
168
|
FunctionExecution = namedtuple("FunctionExecution", ["name", "args", "kwargs"])
|
|
169
169
|
GlobalState.add_command(FunctionExecution(func.__name__, args, kwargs))
|
|
170
|
-
return Success(
|
|
171
|
-
"Command execution appended to command sequence, function not executed in dry_run."
|
|
172
|
-
)
|
|
170
|
+
return Success("Command execution appended to command sequence, function not executed in dry_run.")
|
|
173
171
|
else:
|
|
174
172
|
return func(self, *args, **kwargs)
|
|
175
173
|
|
|
@@ -206,8 +204,7 @@ def parse_format_string(fstring):
|
|
|
206
204
|
|
|
207
205
|
# If this assertion fails, there is a flaw in the algorithm above
|
|
208
206
|
assert tot_n_args == n_args + n_kwargs, (
|
|
209
|
-
f"Total number of arguments ({tot_n_args}) doesn't match # args ({n_args}) + "
|
|
210
|
-
f"# kwargs ({n_kwargs})."
|
|
207
|
+
f"Total number of arguments ({tot_n_args}) doesn't match # args ({n_args}) + # kwargs ({n_kwargs})."
|
|
211
208
|
)
|
|
212
209
|
|
|
213
210
|
if n_args > 0 and n_kwargs > 0:
|
|
@@ -291,9 +288,7 @@ class InvalidCommandExecution(CommandExecution):
|
|
|
291
288
|
self._exc = exc
|
|
292
289
|
|
|
293
290
|
def run(self):
|
|
294
|
-
raise InvalidArgumentsError(
|
|
295
|
-
f"The command {self.get_name()} can not be executed. Reason: {self._exc}"
|
|
296
|
-
)
|
|
291
|
+
raise InvalidArgumentsError(f"The command {self.get_name()} can not be executed. Reason: {self._exc}")
|
|
297
292
|
|
|
298
293
|
def __str__(self):
|
|
299
294
|
msg = super().__str__()
|
|
@@ -307,7 +302,6 @@ class WaitCommand:
|
|
|
307
302
|
self._condition = condition
|
|
308
303
|
|
|
309
304
|
def __call__(self):
|
|
310
|
-
|
|
311
305
|
# .. todo:: do we need a timeout possibility here?
|
|
312
306
|
|
|
313
307
|
while True:
|
|
@@ -330,10 +324,7 @@ class Command:
|
|
|
330
324
|
Arguments can be positional or keyword arguments, not both.
|
|
331
325
|
"""
|
|
332
326
|
|
|
333
|
-
def __init__(
|
|
334
|
-
self, name, cmd, response=None, wait=None, check=None, description=None,
|
|
335
|
-
device_method=None
|
|
336
|
-
):
|
|
327
|
+
def __init__(self, name, cmd, response=None, wait=None, check=None, description=None, device_method=None):
|
|
337
328
|
self._name = name
|
|
338
329
|
self._cmd = cmd
|
|
339
330
|
self._response = response
|
|
@@ -362,7 +353,6 @@ class Command:
|
|
|
362
353
|
return msg
|
|
363
354
|
|
|
364
355
|
def validate_arguments(self, *args, **kwargs):
|
|
365
|
-
|
|
366
356
|
# Special case for commands with *args or **kwargs, we don't validate
|
|
367
357
|
|
|
368
358
|
if self._cmd in ("*", "**"):
|
|
@@ -373,8 +363,7 @@ class Command:
|
|
|
373
363
|
|
|
374
364
|
if self._tot_n_args != nargs + nkwargs:
|
|
375
365
|
raise InvalidArgumentsError(
|
|
376
|
-
f"Expected {self._tot_n_args} arguments for command {self._name}, "
|
|
377
|
-
f"got {nargs + nkwargs} arguments."
|
|
366
|
+
f"Expected {self._tot_n_args} arguments for command {self._name}, got {nargs + nkwargs} arguments."
|
|
378
367
|
)
|
|
379
368
|
|
|
380
369
|
if self._tot_n_args == 0:
|
|
@@ -414,7 +403,6 @@ class Command:
|
|
|
414
403
|
return self._device_method.__name__
|
|
415
404
|
|
|
416
405
|
def get_command_execution(self, *args, **kwargs):
|
|
417
|
-
|
|
418
406
|
return CommandExecution(self, *args, **kwargs)
|
|
419
407
|
|
|
420
408
|
def __call__(self, *args, **kwargs):
|
|
@@ -444,8 +432,7 @@ class Command:
|
|
|
444
432
|
|
|
445
433
|
if self._tot_n_args != nargs + nkwargs:
|
|
446
434
|
raise CommandError(
|
|
447
|
-
f"Expected {self._tot_n_args} arguments for command {self._name}, "
|
|
448
|
-
f"got {nargs + nkwargs} arguments."
|
|
435
|
+
f"Expected {self._tot_n_args} arguments for command {self._name}, got {nargs + nkwargs} arguments."
|
|
449
436
|
)
|
|
450
437
|
|
|
451
438
|
if self._tot_n_args == 0:
|
|
@@ -539,17 +526,13 @@ class ClientServerCommand(Command):
|
|
|
539
526
|
# the class instances are not known at the other side.
|
|
540
527
|
|
|
541
528
|
if self._response.__name__ == "handle_device_method":
|
|
542
|
-
|
|
543
529
|
# call the handle_device_method of the Protocol sub-class
|
|
544
530
|
|
|
545
|
-
logger.log(0,
|
|
546
|
-
f"Executing Command {self._response.__name__}({other!r}, "
|
|
547
|
-
f"{self!r}, {args}, {kwargs})")
|
|
531
|
+
logger.log(0, f"Executing Command {self._response.__name__}({other!r}, {self!r}, {args}, {kwargs})")
|
|
548
532
|
|
|
549
533
|
rc = self._response(other, self, *args, **kwargs)
|
|
550
534
|
else:
|
|
551
|
-
logger.log(0,
|
|
552
|
-
f"Executing Command {self._response.__name__}({other!r}, {args}, {kwargs})")
|
|
535
|
+
logger.log(0, f"Executing Command {self._response.__name__}({other!r}, {args}, {kwargs})")
|
|
553
536
|
|
|
554
537
|
rc = self._response(other, *args, **kwargs)
|
|
555
538
|
|
|
@@ -615,9 +598,7 @@ def get_function(parent_class, method_name: str):
|
|
|
615
598
|
return func
|
|
616
599
|
logger.warning(f"{method_name} is not a function, type={type(func)}")
|
|
617
600
|
else:
|
|
618
|
-
logger.warning(
|
|
619
|
-
f"{parent_class.__module__}.{parent_class.__name__} has no method called {method_name}"
|
|
620
|
-
)
|
|
601
|
+
logger.warning(f"{parent_class.__module__}.{parent_class.__name__} has no method called {method_name}")
|
|
621
602
|
|
|
622
603
|
return None
|
|
623
604
|
|
egse/config.py
CHANGED
|
@@ -20,8 +20,10 @@ from typing import Union
|
|
|
20
20
|
|
|
21
21
|
import git
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
from egse.decorators import deprecate
|
|
24
|
+
|
|
25
|
+
_HERE = Path(__file__).parent.resolve()
|
|
26
|
+
_LOGGER = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def find_first_occurrence_of_dir(pattern: str, root: Path | str = None) -> Optional[Path]:
|
|
@@ -43,7 +45,7 @@ def find_first_occurrence_of_dir(pattern: str, root: Path | str = None) -> Optio
|
|
|
43
45
|
"""
|
|
44
46
|
import fnmatch
|
|
45
47
|
|
|
46
|
-
root = Path(root).resolve() if root else
|
|
48
|
+
root = Path(root).resolve() if root else _HERE
|
|
47
49
|
if not root.is_dir():
|
|
48
50
|
root = root.parent
|
|
49
51
|
|
|
@@ -130,7 +132,7 @@ def find_dirs(pattern: str, root: str = None):
|
|
|
130
132
|
yield Path(path) / name
|
|
131
133
|
|
|
132
134
|
|
|
133
|
-
def find_files(pattern: str, root: str = None, in_dir: str = None):
|
|
135
|
+
def find_files(pattern: str, root: PurePath | str = None, in_dir: str = None):
|
|
134
136
|
"""
|
|
135
137
|
Generator for returning file paths from a top folder, matching the pattern.
|
|
136
138
|
|
|
@@ -171,7 +173,7 @@ def find_files(pattern: str, root: str = None, in_dir: str = None):
|
|
|
171
173
|
|
|
172
174
|
def find_file(name: str, root: str = None, in_dir: str = None) -> Optional[Path]:
|
|
173
175
|
"""
|
|
174
|
-
Find the path to the given file starting from the root directory of the
|
|
176
|
+
Find the path to the given file starting from the root directory of the
|
|
175
177
|
distribution.
|
|
176
178
|
|
|
177
179
|
Note that if there are more files with the given name found in the distribution,
|
|
@@ -202,7 +204,7 @@ def find_file(name: str, root: str = None, in_dir: str = None) -> Optional[Path]
|
|
|
202
204
|
|
|
203
205
|
|
|
204
206
|
def find_root(
|
|
205
|
-
path: Union[str, PurePath], tests: Tuple[str, ...] = (), default: str = None
|
|
207
|
+
path: Union[str, PurePath] | None, tests: Tuple[str, ...] = (), default: str = None
|
|
206
208
|
) -> Union[PurePath, None]:
|
|
207
209
|
"""
|
|
208
210
|
Find the root folder based on the files in ``tests``.
|
|
@@ -240,6 +242,8 @@ def find_root(
|
|
|
240
242
|
|
|
241
243
|
|
|
242
244
|
@lru_cache(maxsize=16)
|
|
245
|
+
@deprecate(reason="the concept of CGSE root doesn't exist in a monorepo.",
|
|
246
|
+
alternative="a case-by-case alternative.")
|
|
243
247
|
def get_common_egse_root(path: Union[str, PurePath] = None) -> Optional[PurePath]:
|
|
244
248
|
"""
|
|
245
249
|
Returns the absolute path to the installation directory for the Common-EGSE.
|
|
@@ -266,7 +270,7 @@ def get_common_egse_root(path: Union[str, PurePath] = None) -> Optional[PurePath
|
|
|
266
270
|
if path is not None:
|
|
267
271
|
return find_root(path, tests=_TEST_NAMES)
|
|
268
272
|
|
|
269
|
-
egse_path: Union[str, PurePath, None] = os.getenv("
|
|
273
|
+
egse_path: Union[str, PurePath, None] = os.getenv("COMMON_EGSE_PATH")
|
|
270
274
|
|
|
271
275
|
if egse_path is None:
|
|
272
276
|
|
|
@@ -285,13 +289,13 @@ def get_common_egse_root(path: Union[str, PurePath] = None) -> Optional[PurePath
|
|
|
285
289
|
git_root = git_repo.git.rev_parse("--show-toplevel")
|
|
286
290
|
egse_path = git_root
|
|
287
291
|
except (git.exc.InvalidGitRepositoryError, git.exc.NoSuchPathError):
|
|
288
|
-
|
|
292
|
+
_LOGGER.info("no git repository found, assuming installation from distribution.")
|
|
289
293
|
egse_path = find_root(_THIS_FILE_LOCATION, tests=_TEST_NAMES)
|
|
290
294
|
|
|
291
|
-
|
|
295
|
+
_LOGGER.debug(f"Common-EGSE location is automatically determined: {egse_path}.")
|
|
292
296
|
|
|
293
297
|
else:
|
|
294
|
-
|
|
298
|
+
_LOGGER.debug(
|
|
295
299
|
f"Common-EGSE location determined from environment variable "
|
|
296
300
|
f"PLATO_COMMON_EGSE_PATH: {egse_path}"
|
|
297
301
|
)
|
|
@@ -368,7 +372,8 @@ def set_logger_levels(logger_levels: List[Tuple] = None):
|
|
|
368
372
|
|
|
369
373
|
|
|
370
374
|
class WorkingDirectory:
|
|
371
|
-
"""
|
|
375
|
+
"""
|
|
376
|
+
WorkingDirectory is a context manager to temporarily change the working directory while
|
|
372
377
|
executing some code.
|
|
373
378
|
|
|
374
379
|
This context manager has a property `path` which returns the absolute path of the
|
|
@@ -402,7 +407,7 @@ class WorkingDirectory:
|
|
|
402
407
|
try:
|
|
403
408
|
os.chdir(self._current_dir)
|
|
404
409
|
except OSError as exc:
|
|
405
|
-
|
|
410
|
+
_LOGGER.warning(f"Change back to previous directory failed: {exc}")
|
|
406
411
|
|
|
407
412
|
@property
|
|
408
413
|
def path(self):
|
egse/control.py
CHANGED
|
@@ -5,7 +5,6 @@ import abc
|
|
|
5
5
|
import logging
|
|
6
6
|
import pickle
|
|
7
7
|
import threading
|
|
8
|
-
import time
|
|
9
8
|
from typing import Any
|
|
10
9
|
|
|
11
10
|
import zmq
|
|
@@ -63,86 +62,6 @@ def is_control_server_active(endpoint: str = None, timeout: float = 0.5) -> bool
|
|
|
63
62
|
return return_code
|
|
64
63
|
|
|
65
64
|
|
|
66
|
-
class Response:
|
|
67
|
-
"""Base class for any reply or response between client-server communication.
|
|
68
|
-
|
|
69
|
-
The idea is that the response is encapsulated in one of the subclasses depending
|
|
70
|
-
on the type of response.
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
def __init__(self, message: str):
|
|
74
|
-
self.message = message
|
|
75
|
-
|
|
76
|
-
def __str__(self):
|
|
77
|
-
return self.message
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def successful(self):
|
|
81
|
-
"""Returns True if the Response is not an Exception."""
|
|
82
|
-
return not isinstance(self, Exception)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
class Failure(Response, Exception):
|
|
86
|
-
"""A failure response indicating something went wrong at the other side.
|
|
87
|
-
|
|
88
|
-
This class is used to encapsulate an Exception that was caught and needs to be
|
|
89
|
-
passed to the client. So, the intended use is like this:
|
|
90
|
-
```
|
|
91
|
-
try:
|
|
92
|
-
# perform some useful action that might raise an Exception
|
|
93
|
-
except SomeException as exc:
|
|
94
|
-
return Failure("Our action failed", exc)
|
|
95
|
-
```
|
|
96
|
-
The client can inspect the Exception that was originally raised, in this case `SomeException`
|
|
97
|
-
with the `cause` variable.
|
|
98
|
-
|
|
99
|
-
Since a Failure is also an Exception, the property `successful` will return False.
|
|
100
|
-
So, the calling method can test for this easily.
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
rc: Response = function_that_returns_a_response()
|
|
104
|
-
|
|
105
|
-
if not rc.successful:
|
|
106
|
-
# handle the failure
|
|
107
|
-
else:
|
|
108
|
-
# handle success
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
def __init__(self, message: str, cause: Exception = None):
|
|
114
|
-
msg = f"{message}: {cause}" if cause is not None else message
|
|
115
|
-
super().__init__(msg)
|
|
116
|
-
self.cause = cause
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
class Success(Response):
|
|
120
|
-
"""A success response for the client.
|
|
121
|
-
|
|
122
|
-
The return code from any action or function that needs to be returned to the
|
|
123
|
-
client shall be added.
|
|
124
|
-
|
|
125
|
-
Since `Success` doesn't inherit from `Exception`, the property `successful` will return True.
|
|
126
|
-
"""
|
|
127
|
-
|
|
128
|
-
def __init__(self, message: str, return_code: Any = None):
|
|
129
|
-
msg = f"{message}: {return_code}" if return_code is not None else message
|
|
130
|
-
super().__init__(msg)
|
|
131
|
-
self.return_code = return_code
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
class Message(Response):
|
|
135
|
-
"""A message response from the client.
|
|
136
|
-
|
|
137
|
-
Send a Message when there is no Failure, but also no return code. This is the alternative of
|
|
138
|
-
returning a None.
|
|
139
|
-
|
|
140
|
-
Message returns True for the property successful since it doesn't inherit from Exception.
|
|
141
|
-
"""
|
|
142
|
-
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
|
|
146
65
|
class ControlServer(metaclass=abc.ABCMeta):
|
|
147
66
|
"""
|
|
148
67
|
The base class for all device control servers and for the Storage Manager and Configuration
|
egse/decorators.py
CHANGED
|
@@ -13,7 +13,7 @@ from typing import Optional
|
|
|
13
13
|
from egse.settings import Settings
|
|
14
14
|
from egse.system import get_caller_info
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
_LOGGER = logging.getLogger(__name__)
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def static_vars(**kwargs):
|
|
@@ -95,7 +95,7 @@ def timer(*, level: int = logging.INFO, precision: int = 4):
|
|
|
95
95
|
value = func(*args, **kwargs)
|
|
96
96
|
end_time = time.perf_counter()
|
|
97
97
|
run_time = end_time - start_time
|
|
98
|
-
|
|
98
|
+
_LOGGER.log(level, f"Finished {func.__name__!r} in {run_time:.{precision}f} secs")
|
|
99
99
|
return value
|
|
100
100
|
|
|
101
101
|
return wrapper_timer
|
|
@@ -160,9 +160,9 @@ def debug(func):
|
|
|
160
160
|
args_repr = [repr(a) for a in args]
|
|
161
161
|
kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
|
|
162
162
|
signature = ", ".join(args_repr + kwargs_repr)
|
|
163
|
-
|
|
163
|
+
_LOGGER.debug(f"Calling {func.__name__}({signature})")
|
|
164
164
|
value = func(*args, **kwargs)
|
|
165
|
-
|
|
165
|
+
_LOGGER.debug(f"{func.__name__!r} returned {value!r}")
|
|
166
166
|
else:
|
|
167
167
|
value = func(*args, **kwargs)
|
|
168
168
|
return value
|
|
@@ -240,10 +240,10 @@ def profile(func):
|
|
|
240
240
|
signature = ", ".join(args_repr + kwargs_repr)
|
|
241
241
|
caller = get_caller_info(level=2)
|
|
242
242
|
prefix = f"PROFILE[{profile.counter}]: "
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
_LOGGER.info(f"{prefix}Calling {func.__name__}({signature})")
|
|
244
|
+
_LOGGER.info(f"{prefix} from {caller.filename} at {caller.lineno}.")
|
|
245
245
|
value = func(*args, **kwargs)
|
|
246
|
-
|
|
246
|
+
_LOGGER.info(f"{prefix}{func.__name__!r} returned {value!r}")
|
|
247
247
|
profile.counter -= 1
|
|
248
248
|
else:
|
|
249
249
|
value = func(*args, **kwargs)
|
|
@@ -257,7 +257,7 @@ def to_be_implemented(func):
|
|
|
257
257
|
|
|
258
258
|
@functools.wraps(func)
|
|
259
259
|
def wrapper_tbi(*args, **kwargs):
|
|
260
|
-
|
|
260
|
+
_LOGGER.warning(f"The function/method {func.__name__} is not yet implemented.")
|
|
261
261
|
return func(*args, **kwargs)
|
|
262
262
|
|
|
263
263
|
return wrapper_tbi
|
egse/device.py
CHANGED
|
@@ -12,8 +12,10 @@ class DeviceConnectionState(IntEnum):
|
|
|
12
12
|
"""Defines connection states for device connections."""
|
|
13
13
|
|
|
14
14
|
# We do not use zero '0' as the connected state to prevent a state to be set
|
|
15
|
-
# to connected by default without it explicitly being set.
|
|
15
|
+
# to connected by default without it explicitly being set. Therefore, 0 will
|
|
16
|
+
# be the state where the connection is not explicitly set.
|
|
16
17
|
|
|
18
|
+
DEVICE_CONNECTION_NOT_SET = 0
|
|
17
19
|
DEVICE_CONNECTED = 1
|
|
18
20
|
DEVICE_NOT_CONNECTED = 2
|
|
19
21
|
|