shepherd-core 2024.4.1__py3-none-any.whl → 2024.5.1__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.
- shepherd_core/__init__.py +3 -3
- shepherd_core/calibration_hw_def.py +9 -1
- shepherd_core/commons.py +2 -0
- shepherd_core/data_models/__init__.py +11 -0
- shepherd_core/data_models/base/__init__.py +4 -1
- shepherd_core/data_models/base/cal_measurement.py +18 -6
- shepherd_core/data_models/base/calibration.py +39 -15
- shepherd_core/data_models/base/content.py +10 -2
- shepherd_core/data_models/base/shepherd.py +21 -12
- shepherd_core/data_models/base/timezone.py +5 -0
- shepherd_core/data_models/base/wrapper.py +3 -3
- shepherd_core/data_models/content/__init__.py +5 -4
- shepherd_core/data_models/content/_external_fixtures.yaml +410 -0
- shepherd_core/data_models/content/energy_environment.py +7 -5
- shepherd_core/data_models/content/energy_environment_fixture.yaml +53 -0
- shepherd_core/data_models/content/firmware.py +12 -5
- shepherd_core/data_models/content/firmware_datatype.py +7 -0
- shepherd_core/data_models/content/virtual_harvester.py +24 -19
- shepherd_core/data_models/content/virtual_harvester_fixture.yaml +160 -0
- shepherd_core/data_models/content/virtual_source.py +22 -10
- shepherd_core/data_models/content/virtual_source_fixture.yaml +230 -0
- shepherd_core/data_models/experiment/__init__.py +5 -4
- shepherd_core/data_models/experiment/experiment.py +7 -6
- shepherd_core/data_models/experiment/observer_features.py +14 -7
- shepherd_core/data_models/experiment/target_config.py +11 -7
- shepherd_core/data_models/readme.md +88 -0
- shepherd_core/data_models/task/__init__.py +10 -3
- shepherd_core/data_models/task/emulation.py +10 -7
- shepherd_core/data_models/task/firmware_mod.py +4 -2
- shepherd_core/data_models/task/harvest.py +5 -4
- shepherd_core/data_models/task/observer_tasks.py +3 -1
- shepherd_core/data_models/task/programming.py +3 -1
- shepherd_core/data_models/task/testbed_tasks.py +3 -1
- shepherd_core/data_models/testbed/__init__.py +5 -2
- shepherd_core/data_models/testbed/cape.py +7 -5
- shepherd_core/data_models/testbed/cape_fixture.yaml +94 -0
- shepherd_core/data_models/testbed/gpio.py +10 -8
- shepherd_core/data_models/testbed/gpio_fixture.yaml +166 -0
- shepherd_core/data_models/testbed/mcu.py +9 -9
- shepherd_core/data_models/testbed/mcu_fixture.yaml +19 -0
- shepherd_core/data_models/testbed/observer.py +9 -4
- shepherd_core/data_models/testbed/observer_fixture.yaml +221 -0
- shepherd_core/data_models/testbed/target.py +4 -2
- shepherd_core/data_models/testbed/target_fixture.yaml +137 -0
- shepherd_core/data_models/testbed/testbed.py +5 -2
- shepherd_core/data_models/testbed/testbed_fixture.yaml +25 -0
- shepherd_core/decoder_waveform/__init__.py +2 -0
- shepherd_core/decoder_waveform/uart.py +43 -25
- shepherd_core/fw_tools/__init__.py +2 -0
- shepherd_core/fw_tools/converter.py +20 -9
- shepherd_core/fw_tools/converter_elf.py +3 -0
- shepherd_core/fw_tools/patcher.py +17 -5
- shepherd_core/fw_tools/validation.py +27 -6
- shepherd_core/inventory/__init__.py +15 -5
- shepherd_core/inventory/python.py +4 -0
- shepherd_core/inventory/system.py +6 -2
- shepherd_core/inventory/target.py +4 -0
- shepherd_core/logger.py +5 -0
- shepherd_core/reader.py +38 -23
- shepherd_core/testbed_client/__init__.py +2 -0
- shepherd_core/testbed_client/cache_path.py +2 -0
- shepherd_core/testbed_client/client.py +15 -8
- shepherd_core/testbed_client/fixtures.py +27 -11
- shepherd_core/testbed_client/user_model.py +8 -3
- shepherd_core/vsource/__init__.py +2 -0
- shepherd_core/vsource/virtual_converter_model.py +10 -3
- shepherd_core/vsource/virtual_harvester_model.py +7 -1
- shepherd_core/vsource/virtual_source_model.py +9 -5
- shepherd_core/writer.py +26 -21
- {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/METADATA +2 -1
- shepherd_core-2024.5.1.dist-info/RECORD +75 -0
- shepherd_core-2024.4.1.dist-info/RECORD +0 -64
- /shepherd_core/data_models/{doc_virtual_source.py → virtual_source_doc.txt} +0 -0
- {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/WHEEL +0 -0
- {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/top_level.txt +0 -0
- {shepherd_core-2024.4.1.dist-info → shepherd_core-2024.5.1.dist-info}/zip-safe +0 -0
shepherd_core/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
1
|
+
"""Bundled core features used by several systems.
|
|
2
|
+
|
|
3
3
|
Provides classes for storing and retrieving sampled IV data to/from
|
|
4
4
|
HDF5 files.
|
|
5
5
|
|
|
@@ -23,7 +23,7 @@ from .testbed_client.client import TestbedClient
|
|
|
23
23
|
from .testbed_client.client import tb_client
|
|
24
24
|
from .writer import Writer
|
|
25
25
|
|
|
26
|
-
__version__ = "2024.
|
|
26
|
+
__version__ = "2024.5.1"
|
|
27
27
|
|
|
28
28
|
__all__ = [
|
|
29
29
|
"Reader",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""parametrized math models for converting between raw- and si-values.
|
|
2
|
+
|
|
3
|
+
Contains some info about the hardware configuration on the shepherd
|
|
2
4
|
cape. Based on these values, one can derive the expected adc readings given
|
|
3
5
|
an input voltage/current or, for emulation, the expected voltage and current
|
|
4
6
|
given the digital code in the DAC.
|
|
@@ -31,6 +33,7 @@ RAW_MAX_DAC = 2**M_DAC - 1
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def adc_current_to_raw(current: float) -> int:
|
|
36
|
+
"""Convert back a current [A] to raw ADC value."""
|
|
34
37
|
# voltage on input of adc
|
|
35
38
|
val_adc = G_INST_AMP * R_SHT * current
|
|
36
39
|
# digital value according to ADC gain
|
|
@@ -39,6 +42,7 @@ def adc_current_to_raw(current: float) -> int:
|
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
def adc_raw_to_current(value: int) -> float:
|
|
45
|
+
"""Convert a raw ADC value to a current [A]."""
|
|
42
46
|
value = min(max(value, 0), 2**M_ADC - 1)
|
|
43
47
|
# voltage on input of adc
|
|
44
48
|
val_adc = float(value) * (G_ADC_I * V_REF_ADC) / (2**M_ADC)
|
|
@@ -47,22 +51,26 @@ def adc_raw_to_current(value: int) -> float:
|
|
|
47
51
|
|
|
48
52
|
|
|
49
53
|
def adc_voltage_to_raw(voltage: float) -> int:
|
|
54
|
+
"""Convert back a voltage [V] to raw ADC value."""
|
|
50
55
|
# digital value according to ADC gain
|
|
51
56
|
val_raw = int(voltage * (2**M_ADC) / (G_ADC_V * V_REF_ADC))
|
|
52
57
|
return min(max(val_raw, 0), 2**M_ADC - 1)
|
|
53
58
|
|
|
54
59
|
|
|
55
60
|
def adc_raw_to_voltage(value: int) -> float:
|
|
61
|
+
"""Convert a raw ADC value to a voltage [V]."""
|
|
56
62
|
value = min(max(value, 0), 2**M_ADC - 1)
|
|
57
63
|
# voltage according to ADC value
|
|
58
64
|
return float(value) * (G_ADC_V * V_REF_ADC) / (2**M_ADC)
|
|
59
65
|
|
|
60
66
|
|
|
61
67
|
def dac_raw_to_voltage(value: int) -> float:
|
|
68
|
+
"""Convert back a raw DAC value to a voltage [V]."""
|
|
62
69
|
value = min(max(value, 0), 2**M_DAC - 1)
|
|
63
70
|
return float(value) * (V_REF_DAC * G_DAC) / (2**M_DAC)
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
def dac_voltage_to_raw(voltage: float) -> int:
|
|
74
|
+
"""Convert a voltage [V] to raw DAC value."""
|
|
67
75
|
val_raw = int(voltage * (2**M_DAC) / (V_REF_DAC * G_DAC))
|
|
68
76
|
return min(max(val_raw, 0), 2**M_DAC - 1)
|
shepherd_core/commons.py
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
"""Container for shepherds data-models.
|
|
2
|
+
|
|
3
|
+
Public models are directly referenced here and are usable like:
|
|
4
|
+
|
|
5
|
+
'''python
|
|
6
|
+
from shepherd-core import data_models
|
|
7
|
+
|
|
8
|
+
cdata = data_models.CapeData(serial_number="A123")
|
|
9
|
+
'''
|
|
10
|
+
"""
|
|
11
|
+
|
|
1
12
|
from .base.calibration import CalibrationCape
|
|
2
13
|
from .base.calibration import CalibrationEmulator
|
|
3
14
|
from .base.calibration import CalibrationHarvester
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Models for the process of calibration a device by measurements."""
|
|
2
|
+
|
|
1
3
|
from typing import List
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
@@ -7,10 +9,10 @@ from pydantic import PositiveFloat
|
|
|
7
9
|
from pydantic import validate_call
|
|
8
10
|
from typing_extensions import Annotated
|
|
9
11
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
12
|
+
from .calibration import CalibrationCape
|
|
13
|
+
from .calibration import CalibrationEmulator
|
|
14
|
+
from .calibration import CalibrationHarvester
|
|
15
|
+
from .calibration import CalibrationPair
|
|
14
16
|
from .calibration import CapeData
|
|
15
17
|
from .shepherd import ShpModel
|
|
16
18
|
|
|
@@ -18,6 +20,8 @@ from .shepherd import ShpModel
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class CalMeasurementPair(ShpModel):
|
|
23
|
+
"""Value-container for a calibration-measurement."""
|
|
24
|
+
|
|
21
25
|
shepherd_raw: PositiveFloat
|
|
22
26
|
reference_si: float = 0
|
|
23
27
|
|
|
@@ -27,7 +31,8 @@ CalMeasPairs = Annotated[List[CalMeasurementPair], Field(min_length=2)]
|
|
|
27
31
|
|
|
28
32
|
@validate_call
|
|
29
33
|
def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
|
|
30
|
-
|
|
34
|
+
"""Convert values from calibration-measurement to the calibration itself."""
|
|
35
|
+
from scipy import stats # placed here due to massive delay
|
|
31
36
|
|
|
32
37
|
x = np.empty(len(data))
|
|
33
38
|
y = np.empty(len(data))
|
|
@@ -40,14 +45,17 @@ def meas_to_cal(data: CalMeasPairs, component: str) -> CalibrationPair:
|
|
|
40
45
|
rval = result.rvalue # test quality of regression
|
|
41
46
|
|
|
42
47
|
if rval < 0.999:
|
|
43
|
-
|
|
48
|
+
msg = (
|
|
44
49
|
"Calibration faulty -> Correlation coefficient "
|
|
45
50
|
f"(rvalue) = {rval}:.6f is too low for '{component}'"
|
|
46
51
|
)
|
|
52
|
+
raise ValueError(msg)
|
|
47
53
|
return CalibrationPair(offset=offset, gain=gain)
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
class CalMeasurementHarvester(ShpModel):
|
|
57
|
+
"""Container for the values of the calibration-measurement."""
|
|
58
|
+
|
|
51
59
|
dac_V_Hrv: CalMeasPairs
|
|
52
60
|
dac_V_Sim: CalMeasPairs
|
|
53
61
|
adc_V_Sense: CalMeasPairs
|
|
@@ -62,6 +70,8 @@ class CalMeasurementHarvester(ShpModel):
|
|
|
62
70
|
|
|
63
71
|
|
|
64
72
|
class CalMeasurementEmulator(ShpModel):
|
|
73
|
+
"""Container for the values of the calibration-measurement."""
|
|
74
|
+
|
|
65
75
|
dac_V_A: CalMeasPairs # TODO: why not V_dac_A or V_dac_a
|
|
66
76
|
dac_V_B: CalMeasPairs
|
|
67
77
|
adc_C_A: CalMeasPairs
|
|
@@ -76,6 +86,8 @@ class CalMeasurementEmulator(ShpModel):
|
|
|
76
86
|
|
|
77
87
|
|
|
78
88
|
class CalMeasurementCape(ShpModel):
|
|
89
|
+
"""Container for the values of the calibration-measurement."""
|
|
90
|
+
|
|
79
91
|
cape: Optional[CapeData] = None
|
|
80
92
|
host: Optional[str] = None
|
|
81
93
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Models for the calibration-data to convert between raw & SI-Values."""
|
|
2
|
+
|
|
1
3
|
import struct
|
|
2
4
|
from typing import Callable
|
|
3
5
|
from typing import Generator
|
|
@@ -26,6 +28,10 @@ Calc_t = TypeVar("Calc_t", NDArray[np.float64], float)
|
|
|
26
28
|
def dict_generator(
|
|
27
29
|
in_dict: Union[dict, list], pre: Optional[list] = None
|
|
28
30
|
) -> Generator[list, None, None]:
|
|
31
|
+
"""Recursive helper-function to generate a 1D-List(or not?).
|
|
32
|
+
|
|
33
|
+
TODO: isn't that a 1D-List generator?
|
|
34
|
+
"""
|
|
29
35
|
pre = pre[:] if pre else []
|
|
30
36
|
if isinstance(in_dict, dict):
|
|
31
37
|
for key, value in in_dict.items():
|
|
@@ -41,13 +47,13 @@ def dict_generator(
|
|
|
41
47
|
|
|
42
48
|
|
|
43
49
|
class CalibrationPair(ShpModel):
|
|
44
|
-
"""SI-value [SI-Unit] = raw-value * gain + offset"""
|
|
50
|
+
"""SI-value [SI-Unit] = raw-value * gain + offset."""
|
|
45
51
|
|
|
46
52
|
gain: PositiveFloat
|
|
47
53
|
offset: float = 0
|
|
48
54
|
|
|
49
55
|
def raw_to_si(self, values_raw: Calc_t, *, allow_negative: bool = True) -> Calc_t:
|
|
50
|
-
"""Convert between physical units and raw unsigned integers"""
|
|
56
|
+
"""Convert between physical units and raw unsigned integers."""
|
|
51
57
|
values_si = values_raw * self.gain + self.offset
|
|
52
58
|
if not allow_negative:
|
|
53
59
|
if isinstance(values_si, np.ndarray):
|
|
@@ -60,17 +66,19 @@ class CalibrationPair(ShpModel):
|
|
|
60
66
|
return values_si
|
|
61
67
|
|
|
62
68
|
def si_to_raw(self, values_si: Calc_t) -> Calc_t:
|
|
63
|
-
"""Convert between physical units and raw unsigned integers"""
|
|
69
|
+
"""Convert between physical units and raw unsigned integers."""
|
|
64
70
|
values_raw = (values_si - self.offset) / self.gain
|
|
65
71
|
if isinstance(values_raw, np.ndarray):
|
|
66
72
|
values_raw[values_raw < 0.0] = 0.0
|
|
67
73
|
values_raw = np.around(values_raw)
|
|
74
|
+
# TODO: overflow should also be prevented (add bit-width) -> fail or warn at both?
|
|
68
75
|
else:
|
|
69
76
|
values_raw = round(max(values_raw, 0.0))
|
|
70
77
|
return values_raw
|
|
71
78
|
|
|
72
79
|
@classmethod
|
|
73
80
|
def from_fn(cls, fn: Callable) -> Self:
|
|
81
|
+
"""Probe linear function to determine scaling values."""
|
|
74
82
|
offset = fn(0)
|
|
75
83
|
gain_inv = fn(1.0) - offset
|
|
76
84
|
return cls(
|
|
@@ -88,13 +96,17 @@ cal_hrv_legacy = { # legacy translator
|
|
|
88
96
|
|
|
89
97
|
|
|
90
98
|
class CalibrationHarvester(ShpModel):
|
|
99
|
+
"""Container for all calibration-pairs for that device."""
|
|
100
|
+
|
|
91
101
|
dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
92
102
|
dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
93
103
|
adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw)
|
|
94
104
|
adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
|
|
95
105
|
|
|
96
106
|
def export_for_sysfs(self) -> dict:
|
|
97
|
-
"""
|
|
107
|
+
"""Convert and write the essential data.
|
|
108
|
+
|
|
109
|
+
[scaling according to commons.h]
|
|
98
110
|
# ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
|
|
99
111
|
# ADC-V is handled in uV (micro-volt), gain is shifted by 8 bit
|
|
100
112
|
# DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
|
|
@@ -111,7 +123,8 @@ class CalibrationHarvester(ShpModel):
|
|
|
111
123
|
if (("gain" in key) and not (0 <= value < 2**32)) or (
|
|
112
124
|
("offset" in key) and not (-(2**31) <= value < 2**31)
|
|
113
125
|
):
|
|
114
|
-
|
|
126
|
+
msg = f"Value ({key}={value}) exceeds uint32-container"
|
|
127
|
+
raise ValueError(msg)
|
|
115
128
|
return cal_set
|
|
116
129
|
|
|
117
130
|
|
|
@@ -124,7 +137,10 @@ cal_emu_legacy = { # legacy translator
|
|
|
124
137
|
|
|
125
138
|
|
|
126
139
|
class CalibrationEmulator(ShpModel):
|
|
127
|
-
"""
|
|
140
|
+
"""Container for all calibration-pairs for that device.
|
|
141
|
+
|
|
142
|
+
Differentiates between both target-ports A/B.
|
|
143
|
+
"""
|
|
128
144
|
|
|
129
145
|
dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
130
146
|
dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
@@ -132,7 +148,9 @@ class CalibrationEmulator(ShpModel):
|
|
|
132
148
|
adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
|
|
133
149
|
|
|
134
150
|
def export_for_sysfs(self) -> dict:
|
|
135
|
-
"""
|
|
151
|
+
"""Convert and write the essential data.
|
|
152
|
+
|
|
153
|
+
[scaling according to commons.h]
|
|
136
154
|
# ADC-C is handled in nA (nano-ampere), gain is shifted by 8 bit
|
|
137
155
|
# ADC-V -> unused by vsrc / emu
|
|
138
156
|
# DAC-V is handled in uV (micro-volt), gain is shifted by 20 bit
|
|
@@ -150,13 +168,15 @@ class CalibrationEmulator(ShpModel):
|
|
|
150
168
|
if (("gain" in key) and not (0 <= value < 2**32)) or (
|
|
151
169
|
("offset" in key) and not (-(2**31) <= value < 2**31)
|
|
152
170
|
):
|
|
153
|
-
|
|
171
|
+
msg = f"Value ({key}={value}) exceeds uint32-container"
|
|
172
|
+
raise ValueError(msg)
|
|
154
173
|
return cal_set
|
|
155
174
|
|
|
156
175
|
|
|
157
176
|
class CapeData(ShpModel):
|
|
158
|
-
"""Representation of Beaglebone Cape information
|
|
159
|
-
|
|
177
|
+
"""Representation of Beaglebone Cape information.
|
|
178
|
+
|
|
179
|
+
User must at least provide serial-number on creation.
|
|
160
180
|
|
|
161
181
|
According to BeagleBone specifications, each cape should host an EEPROM
|
|
162
182
|
that contains some standardized information about the type of cape,
|
|
@@ -178,12 +198,13 @@ class CapeData(ShpModel):
|
|
|
178
198
|
# ⤷ produces something like '2023-01-01'
|
|
179
199
|
|
|
180
200
|
def __repr__(self) -> str: # TODO: override useful?
|
|
181
|
-
"""string-representation allows print(model)"""
|
|
201
|
+
"""string-representation allows print(model)."""
|
|
182
202
|
return str(self.model_dump())
|
|
183
203
|
|
|
184
204
|
|
|
185
205
|
class CalibrationCape(ShpModel):
|
|
186
206
|
"""Represents calibration data of shepherd cape.
|
|
207
|
+
|
|
187
208
|
Defines the format of calibration data and provides convenient functions
|
|
188
209
|
to read and write calibration data.
|
|
189
210
|
|
|
@@ -198,7 +219,8 @@ class CalibrationCape(ShpModel):
|
|
|
198
219
|
|
|
199
220
|
@classmethod
|
|
200
221
|
def from_bytestr(cls, data: bytes, cape: Optional[CapeData] = None) -> Self:
|
|
201
|
-
"""
|
|
222
|
+
"""Instantiate calibration data based on byte string.
|
|
223
|
+
|
|
202
224
|
This is mainly used to deserialize data read from an EEPROM memory.
|
|
203
225
|
|
|
204
226
|
Args:
|
|
@@ -220,11 +242,11 @@ class CalibrationCape(ShpModel):
|
|
|
220
242
|
return cls(**dv)
|
|
221
243
|
|
|
222
244
|
def to_bytestr(self) -> bytes:
|
|
223
|
-
"""
|
|
245
|
+
"""Serialize calibration data to byte string.
|
|
246
|
+
|
|
224
247
|
Used to prepare data for writing it to EEPROM.
|
|
225
248
|
|
|
226
|
-
Returns
|
|
227
|
-
-------
|
|
249
|
+
Returns:
|
|
228
250
|
Byte string representation of calibration values.
|
|
229
251
|
|
|
230
252
|
"""
|
|
@@ -234,6 +256,8 @@ class CalibrationCape(ShpModel):
|
|
|
234
256
|
|
|
235
257
|
|
|
236
258
|
class CalibrationSeries(ShpModel):
|
|
259
|
+
"""Cal-Data for a typical recording of a testbed experiment."""
|
|
260
|
+
|
|
237
261
|
voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9)
|
|
238
262
|
# ⤷ default allows 0 - 12 V in 3 nV-Steps
|
|
239
263
|
current: CalibrationPair = CalibrationPair(gain=250 * 1e-12)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Base-Model for all content."""
|
|
2
|
+
|
|
1
3
|
import hashlib
|
|
2
4
|
from datetime import datetime
|
|
3
5
|
from typing import Optional
|
|
@@ -10,6 +12,7 @@ from pydantic import StringConstraints
|
|
|
10
12
|
from pydantic import model_validator
|
|
11
13
|
from typing_extensions import Annotated
|
|
12
14
|
from typing_extensions import Self
|
|
15
|
+
from typing_extensions import deprecated
|
|
13
16
|
|
|
14
17
|
from .shepherd import ShpModel
|
|
15
18
|
from .timezone import local_now
|
|
@@ -23,15 +26,20 @@ SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
|
|
|
23
26
|
# ⤷ Regex = All Printable ASCII-Characters with Space
|
|
24
27
|
|
|
25
28
|
|
|
29
|
+
@deprecated("use UUID4 instead")
|
|
26
30
|
def id_default() -> int:
|
|
27
|
-
|
|
31
|
+
"""Generate a unique ID - usable as default value.
|
|
32
|
+
|
|
33
|
+
Note: IdInt has space for 128 bit, so 128/4 = 32 hex-chars
|
|
34
|
+
"""
|
|
28
35
|
time_stamp = str(local_now()).encode("utf-8")
|
|
29
36
|
time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-32:]
|
|
30
37
|
return int(time_hash, 16)
|
|
31
38
|
|
|
32
39
|
|
|
33
40
|
class ContentModel(ShpModel):
|
|
34
|
-
|
|
41
|
+
"""Base-Model for content with generalized properties."""
|
|
42
|
+
|
|
35
43
|
# id: UUID4 = Field( # TODO: db-migration - temp fix for documentation
|
|
36
44
|
id: Union[UUID4, int] = Field(
|
|
37
45
|
description="Unique ID",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Shepherds base-model that brings a lot of default functionality."""
|
|
2
|
+
|
|
1
3
|
import hashlib
|
|
2
4
|
import pathlib
|
|
3
5
|
from datetime import timedelta
|
|
@@ -24,14 +26,17 @@ from .wrapper import Wrapper
|
|
|
24
26
|
def path2str(
|
|
25
27
|
dumper: Dumper, data: Union[pathlib.Path, pathlib.WindowsPath, pathlib.PosixPath]
|
|
26
28
|
) -> ScalarNode:
|
|
29
|
+
"""Add a yaml-representation for a specific datatype."""
|
|
27
30
|
return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
def time2int(dumper: Dumper, data: timedelta) -> ScalarNode:
|
|
34
|
+
"""Add a yaml-representation for a specific datatype."""
|
|
31
35
|
return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
def generic2str(dumper: Dumper, data: Any) -> ScalarNode:
|
|
39
|
+
"""Add a yaml-representation for a specific datatype."""
|
|
35
40
|
return dumper.represent_scalar("tag:yaml.org,2002:str", str(data))
|
|
36
41
|
|
|
37
42
|
|
|
@@ -44,7 +49,7 @@ yaml.add_representer(UUID, generic2str, SafeDumper)
|
|
|
44
49
|
|
|
45
50
|
|
|
46
51
|
class ShpModel(BaseModel):
|
|
47
|
-
"""Pre-configured Pydantic Base-Model (specifically for shepherd)
|
|
52
|
+
"""Pre-configured Pydantic Base-Model (specifically for shepherd).
|
|
48
53
|
|
|
49
54
|
Inheritable Features:
|
|
50
55
|
- constant / frozen, hashable .get_hash()
|
|
@@ -69,17 +74,17 @@ class ShpModel(BaseModel):
|
|
|
69
74
|
str_strip_whitespace=True, # strip leading & trailing whitespaces
|
|
70
75
|
use_enum_values=True, # cleaner export of enum-parameters
|
|
71
76
|
allow_inf_nan=False, # float without +-inf or NaN
|
|
72
|
-
# defer_build, possible speedup -> but it triggers a bug
|
|
77
|
+
# defer_build=True, possible speedup -> but it triggers a bug
|
|
73
78
|
)
|
|
74
79
|
|
|
75
80
|
def __repr__(self) -> str:
|
|
76
|
-
"""string-representation allows print(model)"""
|
|
81
|
+
"""string-representation allows print(model)."""
|
|
77
82
|
name = type(self).__name__
|
|
78
83
|
content = self.model_dump(exclude_unset=True, exclude_defaults=True)
|
|
79
84
|
return f"{name}({content})"
|
|
80
85
|
|
|
81
86
|
def __str__(self) -> str:
|
|
82
|
-
"""string-representation allows str(model)"""
|
|
87
|
+
"""string-representation allows str(model)."""
|
|
83
88
|
content = yaml.safe_dump(
|
|
84
89
|
self.model_dump(exclude_unset=True, exclude_defaults=True),
|
|
85
90
|
default_flow_style=False,
|
|
@@ -88,26 +93,29 @@ class ShpModel(BaseModel):
|
|
|
88
93
|
return str(content)
|
|
89
94
|
|
|
90
95
|
def __getitem__(self, key: str) -> Any:
|
|
91
|
-
"""
|
|
96
|
+
"""Allow dict access like model["key"].
|
|
97
|
+
|
|
98
|
+
in addition to model.key.
|
|
99
|
+
"""
|
|
92
100
|
return self.__getattribute__(key)
|
|
93
101
|
|
|
94
102
|
def __contains__(self, item: str) -> bool:
|
|
95
|
-
"""
|
|
103
|
+
"""Allow checks like 'x in YClass'."""
|
|
96
104
|
return item in self.model_dump().keys() # noqa: SIM118
|
|
97
105
|
# more correct, but probably slower than hasattr
|
|
98
106
|
|
|
99
107
|
def keys(self): # noqa: ANN201
|
|
100
|
-
"""Fn of dict"""
|
|
108
|
+
"""Fn of dict."""
|
|
101
109
|
return self.model_dump().keys()
|
|
102
110
|
|
|
103
111
|
def items(self) -> Generator[tuple, None, None]:
|
|
104
|
-
"""Fn of dict"""
|
|
112
|
+
"""Fn of dict."""
|
|
105
113
|
for key in self.keys():
|
|
106
114
|
yield key, self[key]
|
|
107
115
|
|
|
108
116
|
@classmethod
|
|
109
117
|
def schema_to_file(cls, path: Union[str, Path]) -> None:
|
|
110
|
-
"""Store schema to yaml (for frontend-generators)"""
|
|
118
|
+
"""Store schema to yaml (for frontend-generators)."""
|
|
111
119
|
model_dict = cls.model_json_schema()
|
|
112
120
|
model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
|
|
113
121
|
with Path(path).resolve().with_suffix(".yaml").open("w") as f:
|
|
@@ -120,9 +128,10 @@ class ShpModel(BaseModel):
|
|
|
120
128
|
*,
|
|
121
129
|
minimal: bool = True,
|
|
122
130
|
) -> Path:
|
|
123
|
-
"""Store data to yaml in a wrapper
|
|
131
|
+
"""Store data to yaml in a wrapper.
|
|
132
|
+
|
|
124
133
|
minimal: stores minimal set (filters out unset & default parameters)
|
|
125
|
-
comment: documentation
|
|
134
|
+
comment: documentation.
|
|
126
135
|
"""
|
|
127
136
|
model_dict = self.model_dump(exclude_unset=minimal)
|
|
128
137
|
model_wrap = Wrapper(
|
|
@@ -146,7 +155,7 @@ class ShpModel(BaseModel):
|
|
|
146
155
|
|
|
147
156
|
@classmethod
|
|
148
157
|
def from_file(cls, path: Union[str, Path]) -> Self:
|
|
149
|
-
"""Load from yaml"""
|
|
158
|
+
"""Load from yaml."""
|
|
150
159
|
with Path(path).open() as shp_file:
|
|
151
160
|
shp_dict = yaml.safe_load(shp_file)
|
|
152
161
|
shp_wrap = Wrapper(**shp_dict)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Helper-functions for better support of timezones."""
|
|
2
|
+
|
|
1
3
|
import time
|
|
2
4
|
from datetime import datetime
|
|
3
5
|
from datetime import timedelta
|
|
@@ -5,14 +7,17 @@ from datetime import timezone
|
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def local_tz() -> timezone:
|
|
10
|
+
"""Query the local timezone of the user."""
|
|
8
11
|
if time.daylight:
|
|
9
12
|
return timezone(timedelta(seconds=-time.altzone), time.tzname[1])
|
|
10
13
|
return timezone(timedelta(seconds=-time.timezone), time.tzname[0])
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def local_now() -> datetime:
|
|
17
|
+
"""Query the local date-time of the user."""
|
|
14
18
|
return datetime.now(tz=local_tz())
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
def local_iso_date() -> str:
|
|
22
|
+
"""Query the local date-time of the user as a ISO 8601 date."""
|
|
18
23
|
return local_now().date().isoformat()
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""Wrapper-related ecosystem for transferring models."""
|
|
2
|
+
|
|
1
3
|
from datetime import datetime
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
@@ -10,9 +12,7 @@ SafeStrClone = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
|
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class Wrapper(BaseModel):
|
|
13
|
-
"""
|
|
14
|
-
all models with dynamic typecasting
|
|
15
|
-
"""
|
|
15
|
+
"""Generalized web- & file-interface for all models with dynamic typecasting."""
|
|
16
16
|
|
|
17
17
|
datatype: str
|
|
18
18
|
# ⤷ model-name
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""Module for content-type data-models.
|
|
2
|
+
|
|
3
|
+
These models import externally from: /base, /testbed.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
from .energy_environment import EnergyDType
|
|
2
7
|
from .energy_environment import EnergyEnvironment
|
|
3
8
|
from .firmware import Firmware
|
|
@@ -5,8 +10,6 @@ from .firmware_datatype import FirmwareDType
|
|
|
5
10
|
from .virtual_harvester import VirtualHarvesterConfig
|
|
6
11
|
from .virtual_source import VirtualSourceConfig
|
|
7
12
|
|
|
8
|
-
# these models import externally from: /base, /testbed
|
|
9
|
-
|
|
10
13
|
__all__ = [
|
|
11
14
|
"EnergyEnvironment",
|
|
12
15
|
"VirtualSourceConfig",
|
|
@@ -16,5 +19,3 @@ __all__ = [
|
|
|
16
19
|
"EnergyDType",
|
|
17
20
|
"FirmwareDType",
|
|
18
21
|
]
|
|
19
|
-
|
|
20
|
-
# TODO: include models for end-users on root-level
|