shepherd-core 2023.8.6__py3-none-any.whl → 2023.8.8__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 +1 -1
- shepherd_core/data_models/__init__.py +3 -1
- shepherd_core/data_models/base/cal_measurement.py +17 -14
- shepherd_core/data_models/base/calibration.py +41 -8
- shepherd_core/data_models/base/content.py +17 -13
- shepherd_core/data_models/base/shepherd.py +29 -22
- shepherd_core/data_models/base/wrapper.py +5 -4
- shepherd_core/data_models/content/energy_environment.py +3 -2
- shepherd_core/data_models/content/firmware.py +10 -6
- shepherd_core/data_models/content/virtual_harvester.py +42 -39
- shepherd_core/data_models/content/virtual_source.py +83 -72
- shepherd_core/data_models/doc_virtual_source.py +7 -14
- shepherd_core/data_models/experiment/experiment.py +20 -15
- shepherd_core/data_models/experiment/observer_features.py +33 -31
- shepherd_core/data_models/experiment/target_config.py +24 -18
- shepherd_core/data_models/task/__init__.py +13 -5
- shepherd_core/data_models/task/emulation.py +35 -23
- shepherd_core/data_models/task/firmware_mod.py +14 -13
- shepherd_core/data_models/task/harvest.py +28 -13
- shepherd_core/data_models/task/observer_tasks.py +17 -7
- shepherd_core/data_models/task/programming.py +13 -13
- shepherd_core/data_models/task/testbed_tasks.py +16 -6
- shepherd_core/data_models/testbed/cape.py +3 -2
- shepherd_core/data_models/testbed/gpio.py +18 -15
- shepherd_core/data_models/testbed/mcu.py +7 -6
- shepherd_core/data_models/testbed/observer.py +23 -19
- shepherd_core/data_models/testbed/target.py +15 -14
- shepherd_core/data_models/testbed/testbed.py +14 -11
- shepherd_core/fw_tools/converter.py +7 -7
- shepherd_core/fw_tools/converter_elf.py +2 -2
- shepherd_core/fw_tools/patcher.py +7 -6
- shepherd_core/fw_tools/validation.py +3 -3
- shepherd_core/inventory/__init__.py +16 -8
- shepherd_core/inventory/python.py +4 -3
- shepherd_core/inventory/system.py +5 -5
- shepherd_core/inventory/target.py +4 -4
- shepherd_core/reader.py +3 -3
- shepherd_core/testbed_client/client.py +6 -4
- shepherd_core/testbed_client/user_model.py +14 -10
- shepherd_core/writer.py +2 -2
- {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/METADATA +9 -2
- {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/RECORD +49 -49
- {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/WHEEL +1 -1
- tests/data_models/example_cal_data.yaml +2 -1
- tests/data_models/example_cal_meas.yaml +2 -1
- tests/data_models/test_base_models.py +19 -2
- tests/inventory/test_inventory.py +1 -1
- {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/top_level.txt +0 -0
- {shepherd_core-2023.8.6.dist-info → shepherd_core-2023.8.8.dist-info}/zip-safe +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import
|
|
4
|
-
from pydantic import
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic import model_validator
|
|
5
|
+
from typing_extensions import Annotated
|
|
5
6
|
|
|
6
7
|
from ...commons import samplerate_sps_default
|
|
7
8
|
from ...logger import logger
|
|
@@ -11,6 +12,12 @@ from ..base.content import ContentModel
|
|
|
11
12
|
from .virtual_harvester import HarvesterPRUConfig
|
|
12
13
|
from .virtual_harvester import VirtualHarvesterConfig
|
|
13
14
|
|
|
15
|
+
# Custom Types
|
|
16
|
+
LUT_SIZE: int = 12
|
|
17
|
+
NormedNum = Annotated[float, Field(ge=0.0, le=1.0)]
|
|
18
|
+
LUT1D = Annotated[List[NormedNum], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]
|
|
19
|
+
LUT2D = Annotated[List[LUT1D], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]
|
|
20
|
+
|
|
14
21
|
|
|
15
22
|
class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
16
23
|
"""The virtual Source uses the energy environment (file)
|
|
@@ -30,89 +37,82 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
30
37
|
enable_buck: bool = False
|
|
31
38
|
# ⤷ if false -> v_output = v_intermediate
|
|
32
39
|
|
|
33
|
-
interval_startup_delay_drain_ms:
|
|
40
|
+
interval_startup_delay_drain_ms: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
34
41
|
|
|
35
42
|
harvester: VirtualHarvesterConfig = VirtualHarvesterConfig(name="mppt_opt")
|
|
36
43
|
|
|
37
|
-
V_input_max_mV:
|
|
38
|
-
I_input_max_mA:
|
|
39
|
-
V_input_drop_mV:
|
|
44
|
+
V_input_max_mV: Annotated[float, Field(ge=0, le=10_000)] = 10_000
|
|
45
|
+
I_input_max_mA: Annotated[float, Field(ge=0, le=4.29e3)] = 4_200
|
|
46
|
+
V_input_drop_mV: Annotated[float, Field(ge=0, le=4.29e6)] = 0
|
|
40
47
|
# ⤷ simulate input-diode
|
|
41
|
-
R_input_mOhm:
|
|
48
|
+
R_input_mOhm: Annotated[float, Field(ge=0, le=4.29e6)] = 0
|
|
42
49
|
# ⤷ resistance only active with disabled boost, range [1 mOhm; 1MOhm]
|
|
43
50
|
|
|
44
51
|
# primary storage-Cap
|
|
45
|
-
C_intermediate_uF:
|
|
46
|
-
V_intermediate_init_mV:
|
|
52
|
+
C_intermediate_uF: Annotated[float, Field(ge=0, le=100_000)] = 0
|
|
53
|
+
V_intermediate_init_mV: Annotated[float, Field(ge=0, le=10_000)] = 3_000
|
|
47
54
|
# ⤷ allow a proper / fast startup
|
|
48
|
-
I_intermediate_leak_nA:
|
|
55
|
+
I_intermediate_leak_nA: Annotated[float, Field(ge=0, le=4.29e9)] = 0
|
|
49
56
|
|
|
50
|
-
V_intermediate_enable_threshold_mV:
|
|
57
|
+
V_intermediate_enable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 1
|
|
51
58
|
# ⤷ target gets connected (hysteresis-combo with next value)
|
|
52
|
-
V_intermediate_disable_threshold_mV:
|
|
59
|
+
V_intermediate_disable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
53
60
|
# ⤷ target gets disconnected
|
|
54
|
-
interval_check_thresholds_ms:
|
|
61
|
+
interval_check_thresholds_ms: Annotated[float, Field(ge=0, le=4.29e3)] = 0
|
|
55
62
|
# ⤷ some ICs (BQ) check every 64 ms if output should be disconnected
|
|
56
63
|
|
|
57
64
|
# pwr-good: target is informed on output-pin (hysteresis) -> for intermediate voltage
|
|
58
|
-
V_pwr_good_enable_threshold_mV:
|
|
59
|
-
V_pwr_good_disable_threshold_mV:
|
|
65
|
+
V_pwr_good_enable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 2_800
|
|
66
|
+
V_pwr_good_disable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 2200
|
|
60
67
|
immediate_pwr_good_signal: bool = True
|
|
61
68
|
# ⤷ 1: activate instant schmitt-trigger, 0: stay in interval for checking thresholds
|
|
62
69
|
|
|
63
70
|
# final (always last) stage to compensate undetectable current spikes
|
|
64
71
|
# when enabling power for target
|
|
65
|
-
C_output_uF:
|
|
72
|
+
C_output_uF: Annotated[float, Field(ge=0, le=4.29e6)] = 1.0
|
|
66
73
|
|
|
67
74
|
# Extra
|
|
68
|
-
V_output_log_gpio_threshold_mV:
|
|
75
|
+
V_output_log_gpio_threshold_mV: Annotated[float, Field(ge=0, le=4.29e6)] = 1_400
|
|
69
76
|
# ⤷ min voltage needed to enable recording changes in gpio-bank
|
|
70
77
|
|
|
71
78
|
# Boost Converter
|
|
72
|
-
V_input_boost_threshold_mV:
|
|
79
|
+
V_input_boost_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
73
80
|
# ⤷ min input-voltage for the boost converter to work
|
|
74
|
-
V_intermediate_max_mV:
|
|
81
|
+
V_intermediate_max_mV: Annotated[float, Field(ge=0, le=10_000)] = 10_000
|
|
75
82
|
# ⤷ boost converter shuts off
|
|
76
83
|
|
|
77
|
-
LUT_input_efficiency:
|
|
78
|
-
item_type=conlist(confloat(ge=0.0, le=1.0), min_items=12, max_items=12),
|
|
79
|
-
min_items=12,
|
|
80
|
-
max_items=12,
|
|
81
|
-
) = 12 * [12 * [1.00]]
|
|
84
|
+
LUT_input_efficiency: LUT2D = 12 * [12 * [1.00]]
|
|
82
85
|
# ⤷ rows are current -> first row a[V=0][:]
|
|
83
86
|
# input-LUT[12][12] depending on array[inp_voltage][log(inp_current)],
|
|
84
87
|
# influence of cap-voltage is not implemented
|
|
85
|
-
LUT_input_V_min_log2_uV:
|
|
88
|
+
LUT_input_V_min_log2_uV: Annotated[int, Field(ge=0, le=20)] = 0
|
|
86
89
|
# ⤷ 2^7 = 128 uV -> LUT[0][:] is for inputs < 128 uV
|
|
87
|
-
LUT_input_I_min_log2_nA:
|
|
90
|
+
LUT_input_I_min_log2_nA: Annotated[int, Field(ge=0, le=20)] = 0
|
|
88
91
|
# ⤷ 2^8 = 256 nA -> LUT[:][0] is for inputs < 256 nA
|
|
89
92
|
|
|
90
93
|
# Buck Converter
|
|
91
|
-
V_output_mV:
|
|
92
|
-
V_buck_drop_mV:
|
|
94
|
+
V_output_mV: Annotated[float, Field(ge=0, le=5_000)] = 2_400
|
|
95
|
+
V_buck_drop_mV: Annotated[float, Field(ge=0, le=5_000)] = 0
|
|
93
96
|
# ⤷ simulate LDO min voltage differential or output-diode
|
|
94
97
|
|
|
95
|
-
LUT_output_efficiency:
|
|
96
|
-
item_type=confloat(ge=0.0, le=1.0),
|
|
97
|
-
min_items=12,
|
|
98
|
-
max_items=12,
|
|
99
|
-
) = 12 * [1.00]
|
|
98
|
+
LUT_output_efficiency: LUT1D = 12 * [1.00]
|
|
100
99
|
# ⤷ array[12] depending on output_current
|
|
101
|
-
LUT_output_I_min_log2_nA:
|
|
100
|
+
LUT_output_I_min_log2_nA: Annotated[int, Field(ge=0, le=20)] = 0
|
|
102
101
|
# ⤷ 2^8 = 256 nA -> LUT[0] is for inputs < 256 nA, see notes on LUT_input for explanation
|
|
103
102
|
|
|
104
|
-
@
|
|
103
|
+
@model_validator(mode="before")
|
|
104
|
+
@classmethod
|
|
105
105
|
def query_database(cls, values: dict) -> dict:
|
|
106
106
|
values, chain = tb_client.try_completing_model(cls.__name__, values)
|
|
107
107
|
values = tb_client.fill_in_user_data(values)
|
|
108
108
|
logger.debug("VSrc-Inheritances: %s", chain)
|
|
109
109
|
return values
|
|
110
110
|
|
|
111
|
-
@
|
|
112
|
-
def post_validation(
|
|
111
|
+
@model_validator(mode="after")
|
|
112
|
+
def post_validation(self):
|
|
113
113
|
# trigger stricter test of harv-parameters
|
|
114
|
-
HarvesterPRUConfig.from_vhrv(
|
|
115
|
-
return
|
|
114
|
+
HarvesterPRUConfig.from_vhrv(self.harvester, for_emu=True)
|
|
115
|
+
return self
|
|
116
116
|
|
|
117
117
|
def calc_internal_states(self) -> dict:
|
|
118
118
|
"""
|
|
@@ -220,15 +220,16 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
220
220
|
return int((10**3 * (2**28)) // (C_cap_uF * samplerate_sps_default))
|
|
221
221
|
|
|
222
222
|
|
|
223
|
-
u32 =
|
|
224
|
-
u8 =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
|
|
223
|
+
u32 = Annotated[int, Field(ge=0, lt=2**32)]
|
|
224
|
+
u8 = Annotated[int, Field(ge=0, lt=2**8)]
|
|
225
|
+
lut_i = Annotated[
|
|
226
|
+
List[Annotated[List[u8], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]],
|
|
227
|
+
Field(
|
|
228
|
+
min_length=LUT_SIZE,
|
|
229
|
+
max_length=LUT_SIZE,
|
|
230
|
+
),
|
|
231
|
+
]
|
|
232
|
+
lut_o = Annotated[List[u32], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]
|
|
232
233
|
|
|
233
234
|
|
|
234
235
|
class ConverterPRUConfig(ShpModel):
|
|
@@ -282,32 +283,42 @@ class ConverterPRUConfig(ShpModel):
|
|
|
282
283
|
return cls(
|
|
283
284
|
# General
|
|
284
285
|
converter_mode=data.calc_converter_mode(log_intermediate_node),
|
|
285
|
-
interval_startup_delay_drain_n=
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
V_input_max_uV=data.V_input_max_mV * 1e3,
|
|
289
|
-
I_input_max_nA=data.I_input_max_mA * 1e6,
|
|
290
|
-
V_input_drop_uV=data.V_input_drop_mV * 1e3,
|
|
291
|
-
R_input_kOhm_n22=data.R_input_mOhm * (1e-6 * 2**22),
|
|
286
|
+
interval_startup_delay_drain_n=round(
|
|
287
|
+
data.interval_startup_delay_drain_ms * samplerate_sps_default * 1e-3
|
|
288
|
+
),
|
|
289
|
+
V_input_max_uV=round(data.V_input_max_mV * 1e3),
|
|
290
|
+
I_input_max_nA=round(data.I_input_max_mA * 1e6),
|
|
291
|
+
V_input_drop_uV=round(data.V_input_drop_mV * 1e3),
|
|
292
|
+
R_input_kOhm_n22=round(data.R_input_mOhm * (1e-6 * 2**22)),
|
|
292
293
|
Constant_us_per_nF_n28=data.calc_cap_constant_us_per_nF_n28(),
|
|
293
|
-
V_intermediate_init_uV=data.V_intermediate_init_mV * 1e3,
|
|
294
|
-
I_intermediate_leak_nA=data.I_intermediate_leak_nA,
|
|
295
|
-
V_enable_output_threshold_uV=
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
294
|
+
V_intermediate_init_uV=round(data.V_intermediate_init_mV * 1e3),
|
|
295
|
+
I_intermediate_leak_nA=round(data.I_intermediate_leak_nA),
|
|
296
|
+
V_enable_output_threshold_uV=round(
|
|
297
|
+
states["V_enable_output_threshold_mV"] * 1e3
|
|
298
|
+
),
|
|
299
|
+
V_disable_output_threshold_uV=round(
|
|
300
|
+
states["V_disable_output_threshold_mV"] * 1e3
|
|
301
|
+
),
|
|
302
|
+
dV_enable_output_uV=round(states["dV_enable_output_mV"] * 1e3),
|
|
303
|
+
interval_check_thresholds_n=round(
|
|
304
|
+
data.interval_check_thresholds_ms * samplerate_sps_default * 1e-3
|
|
305
|
+
),
|
|
306
|
+
V_pwr_good_enable_threshold_uV=round(
|
|
307
|
+
data.V_pwr_good_enable_threshold_mV * 1e3
|
|
308
|
+
),
|
|
309
|
+
V_pwr_good_disable_threshold_uV=round(
|
|
310
|
+
data.V_pwr_good_disable_threshold_mV * 1e3
|
|
311
|
+
),
|
|
303
312
|
immediate_pwr_good_signal=data.immediate_pwr_good_signal,
|
|
304
|
-
V_output_log_gpio_threshold_uV=
|
|
313
|
+
V_output_log_gpio_threshold_uV=round(
|
|
314
|
+
data.V_output_log_gpio_threshold_mV * 1e3
|
|
315
|
+
),
|
|
305
316
|
# Boost-Converter
|
|
306
|
-
V_input_boost_threshold_uV=data.V_input_boost_threshold_mV * 1e3,
|
|
307
|
-
V_intermediate_max_uV=data.V_intermediate_max_mV * 1e3,
|
|
317
|
+
V_input_boost_threshold_uV=round(data.V_input_boost_threshold_mV * 1e3),
|
|
318
|
+
V_intermediate_max_uV=round(data.V_intermediate_max_mV * 1e3),
|
|
308
319
|
# Buck-Converter
|
|
309
|
-
V_output_uV=data.V_output_mV * 1e3,
|
|
310
|
-
V_buck_drop_uV=data.V_buck_drop_mV * 1e3,
|
|
320
|
+
V_output_uV=round(data.V_output_mV * 1e3),
|
|
321
|
+
V_buck_drop_uV=round(data.V_buck_drop_mV * 1e3),
|
|
311
322
|
# LUTs
|
|
312
323
|
LUT_input_V_min_log2_uV=data.LUT_input_V_min_log2_uV,
|
|
313
324
|
LUT_input_I_min_log2_nA=data.LUT_input_I_min_log2_nA,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
|
-
from pydantic import
|
|
5
|
-
from pydantic import conlist
|
|
6
|
-
from pydantic import root_validator
|
|
4
|
+
from pydantic import model_validator
|
|
7
5
|
|
|
8
6
|
from .. import logger
|
|
9
7
|
from ..data_models import Fixture
|
|
10
8
|
from ..data_models import ShpModel
|
|
11
9
|
from .content import VirtualHarvesterConfig
|
|
10
|
+
from .content.virtual_source import LUT1D
|
|
11
|
+
from .content.virtual_source import LUT2D
|
|
12
12
|
|
|
13
13
|
fixture_path = Path(__file__).resolve().with_name("content/virtual_source_fixture.yaml")
|
|
14
14
|
fixtures = Fixture(fixture_path, "content.VirtualSource")
|
|
@@ -168,11 +168,7 @@ class VirtualSourceDoc(ShpModel, title="Virtual Source (Documented, Testversion)
|
|
|
168
168
|
le=10_000,
|
|
169
169
|
)
|
|
170
170
|
|
|
171
|
-
LUT_input_efficiency:
|
|
172
|
-
item_type=conlist(confloat(ge=0.0, le=1.0), min_items=12, max_items=12),
|
|
173
|
-
min_items=12,
|
|
174
|
-
max_items=12,
|
|
175
|
-
) = Field(
|
|
171
|
+
LUT_input_efficiency: LUT2D = Field(
|
|
176
172
|
description="# input-array[12][12] depending on "
|
|
177
173
|
"array[inp_voltage][log(inp_current)], "
|
|
178
174
|
"influence of cap-voltage is not implemented",
|
|
@@ -206,11 +202,7 @@ class VirtualSourceDoc(ShpModel, title="Virtual Source (Documented, Testversion)
|
|
|
206
202
|
le=5_000,
|
|
207
203
|
)
|
|
208
204
|
|
|
209
|
-
LUT_output_efficiency:
|
|
210
|
-
item_type=confloat(ge=0.0, le=1.0),
|
|
211
|
-
min_items=12,
|
|
212
|
-
max_items=12,
|
|
213
|
-
) = Field(
|
|
205
|
+
LUT_output_efficiency: LUT1D = Field(
|
|
214
206
|
description="Output-Array[12] depending on output_current. In & Output is linear",
|
|
215
207
|
default=12 * [1.00],
|
|
216
208
|
)
|
|
@@ -222,7 +214,8 @@ class VirtualSourceDoc(ShpModel, title="Virtual Source (Documented, Testversion)
|
|
|
222
214
|
le=20,
|
|
223
215
|
)
|
|
224
216
|
|
|
225
|
-
@
|
|
217
|
+
@model_validator(mode="before")
|
|
218
|
+
@classmethod
|
|
226
219
|
def query_database(cls, values: dict) -> dict:
|
|
227
220
|
values = fixtures.try_completing_model(values)
|
|
228
221
|
values, chain = fixtures.try_inheritance(values)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from datetime import timedelta
|
|
3
|
+
from typing import List
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
from pydantic import EmailStr
|
|
6
7
|
from pydantic import Field
|
|
7
|
-
from pydantic import
|
|
8
|
-
from
|
|
8
|
+
from pydantic import model_validator
|
|
9
|
+
from typing_extensions import Annotated
|
|
9
10
|
|
|
10
11
|
from ..base.content import IdInt
|
|
11
12
|
from ..base.content import NameStr
|
|
@@ -27,8 +28,11 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
27
28
|
description="Unique ID",
|
|
28
29
|
default_factory=id_default,
|
|
29
30
|
)
|
|
31
|
+
# ⤷ TODO: automatic ID is problematic for identification by hash
|
|
30
32
|
name: NameStr
|
|
31
|
-
description:
|
|
33
|
+
description: Annotated[
|
|
34
|
+
Optional[SafeStr], Field(description="Required for public instances")
|
|
35
|
+
] = None
|
|
32
36
|
comment: Optional[SafeStr] = None
|
|
33
37
|
created: datetime = Field(default_factory=datetime.now)
|
|
34
38
|
|
|
@@ -36,7 +40,8 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
36
40
|
owner_id: Optional[IdInt] = 5472 # UUID?
|
|
37
41
|
|
|
38
42
|
# feedback
|
|
39
|
-
email_results: Optional[EmailStr]
|
|
43
|
+
email_results: Optional[EmailStr] = None
|
|
44
|
+
# ⤷ TODO: can be bool, as its linked to account
|
|
40
45
|
sys_logging: SystemLogging = SystemLogging(dmesg=True, ptp=False, shepherd=True)
|
|
41
46
|
|
|
42
47
|
# schedule
|
|
@@ -45,21 +50,21 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
45
50
|
abort_on_error: bool = False
|
|
46
51
|
|
|
47
52
|
# targets
|
|
48
|
-
target_configs:
|
|
53
|
+
target_configs: Annotated[List[TargetConfig], Field(min_length=1, max_length=64)]
|
|
49
54
|
|
|
50
|
-
@
|
|
51
|
-
def post_validation(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
+
@model_validator(mode="after")
|
|
56
|
+
def post_validation(self):
|
|
57
|
+
self.validate_targets(self.target_configs)
|
|
58
|
+
self.validate_observers(self.target_configs)
|
|
59
|
+
if self.duration and self.duration.total_seconds() < 0:
|
|
55
60
|
raise ValueError("Duration of experiment can't be negative.")
|
|
56
|
-
return
|
|
61
|
+
return self
|
|
57
62
|
|
|
58
63
|
@staticmethod
|
|
59
|
-
def validate_targets(
|
|
64
|
+
def validate_targets(configs: List[TargetConfig]) -> None:
|
|
60
65
|
target_ids = []
|
|
61
66
|
custom_ids = []
|
|
62
|
-
for _config in
|
|
67
|
+
for _config in configs:
|
|
63
68
|
for _id in _config.target_IDs:
|
|
64
69
|
target_ids.append(_id)
|
|
65
70
|
Target(id=_id)
|
|
@@ -76,9 +81,9 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
76
81
|
)
|
|
77
82
|
|
|
78
83
|
@staticmethod
|
|
79
|
-
def validate_observers(
|
|
84
|
+
def validate_observers(configs: List[TargetConfig]) -> None:
|
|
80
85
|
target_ids = []
|
|
81
|
-
for _config in
|
|
86
|
+
for _config in configs:
|
|
82
87
|
for _id in _config.target_IDs:
|
|
83
88
|
target_ids.append(_id)
|
|
84
89
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
from enum import Enum
|
|
3
|
+
from typing import List
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
7
|
+
from pydantic import Field
|
|
6
8
|
from pydantic import PositiveFloat
|
|
7
|
-
from pydantic import
|
|
8
|
-
from
|
|
9
|
-
from pydantic import conlist
|
|
10
|
-
from pydantic import root_validator
|
|
9
|
+
from pydantic import model_validator
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
11
|
|
|
12
12
|
from ..base.shepherd import ShpModel
|
|
13
13
|
from ..testbed.gpio import GPIO
|
|
@@ -28,26 +28,26 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
28
28
|
|
|
29
29
|
# post-processing
|
|
30
30
|
calculate_power: bool = False
|
|
31
|
-
samplerate:
|
|
31
|
+
samplerate: Annotated[int, Field(ge=10, le=100_000)] = 100_000 # down-sample
|
|
32
32
|
discard_current: bool = False
|
|
33
33
|
discard_voltage: bool = False
|
|
34
34
|
# ⤷ reduce file-size by omitting current / voltage
|
|
35
35
|
|
|
36
|
-
@
|
|
37
|
-
def post_validation(
|
|
38
|
-
if
|
|
36
|
+
@model_validator(mode="after")
|
|
37
|
+
def post_validation(self):
|
|
38
|
+
if self.delay and self.delay.total_seconds() < 0:
|
|
39
39
|
raise ValueError("Delay can't be negative.")
|
|
40
|
-
if
|
|
40
|
+
if self.duration and self.duration.total_seconds() < 0:
|
|
41
41
|
raise ValueError("Duration can't be negative.")
|
|
42
42
|
|
|
43
|
-
discard_all =
|
|
44
|
-
if not
|
|
43
|
+
discard_all = self.discard_current and self.discard_voltage
|
|
44
|
+
if not self.calculate_power and discard_all:
|
|
45
45
|
raise ValueError(
|
|
46
46
|
"Error in config -> tracing enabled, but output gets discarded"
|
|
47
47
|
)
|
|
48
|
-
if
|
|
48
|
+
if self.calculate_power:
|
|
49
49
|
raise ValueError("postprocessing not implemented ATM")
|
|
50
|
-
return
|
|
50
|
+
return self
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
@@ -56,10 +56,12 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
58
|
# initial recording
|
|
59
|
-
mask:
|
|
59
|
+
mask: Annotated[int, Field(ge=0, lt=2**10)] = 0b11_1111_1111 # all
|
|
60
60
|
# ⤷ TODO: custom mask not implemented
|
|
61
|
-
gpios: Optional[
|
|
62
|
-
|
|
61
|
+
gpios: Optional[
|
|
62
|
+
Annotated[List[GPIO], Field(min_length=1, max_length=10)]
|
|
63
|
+
] = None # = all
|
|
64
|
+
# ⤷ TODO: list of GPIO to build mask, one of both should be internal / computed field
|
|
63
65
|
|
|
64
66
|
# time
|
|
65
67
|
delay: timedelta = 0 # seconds
|
|
@@ -68,18 +70,18 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
68
70
|
# post-processing,
|
|
69
71
|
uart_decode: bool = False # todo: is currently done online by system-service
|
|
70
72
|
uart_pin: GPIO = GPIO(name="GPIO8")
|
|
71
|
-
uart_baudrate:
|
|
73
|
+
uart_baudrate: Annotated[int, Field(ge=2_400, le=921_600)] = 115_200
|
|
72
74
|
# TODO: add a "discard_gpio" (if only uart is wanted)
|
|
73
75
|
|
|
74
|
-
@
|
|
75
|
-
def post_validation(
|
|
76
|
-
if
|
|
76
|
+
@model_validator(mode="after")
|
|
77
|
+
def post_validation(self):
|
|
78
|
+
if self.mask == 0:
|
|
77
79
|
raise ValueError("Error in config -> tracing enabled but mask is 0")
|
|
78
|
-
if
|
|
80
|
+
if self.delay and self.delay.total_seconds() < 0:
|
|
79
81
|
raise ValueError("Delay can't be negative.")
|
|
80
|
-
if
|
|
82
|
+
if self.duration and self.duration.total_seconds() < 0:
|
|
81
83
|
raise ValueError("Duration can't be negative.")
|
|
82
|
-
return
|
|
84
|
+
return self
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
class GpioLevel(str, Enum):
|
|
@@ -96,17 +98,17 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
|
|
|
96
98
|
# ⤷ resolution 10 us (guaranteed, but finer steps are possible)
|
|
97
99
|
gpio: GPIO
|
|
98
100
|
level: GpioLevel
|
|
99
|
-
period:
|
|
101
|
+
period: Annotated[float, Field(ge=10e-6)] = 1
|
|
100
102
|
# ⤷ time base of periodicity in s
|
|
101
|
-
count:
|
|
103
|
+
count: Annotated[int, Field(ge=1, le=4096)] = 1
|
|
102
104
|
|
|
103
|
-
@
|
|
104
|
-
def post_validation(
|
|
105
|
-
if not
|
|
105
|
+
@model_validator(mode="after")
|
|
106
|
+
def post_validation(self):
|
|
107
|
+
if not self.gpio.user_controllable():
|
|
106
108
|
raise ValueError(
|
|
107
|
-
f"GPIO '{
|
|
109
|
+
f"GPIO '{self.gpio.name}' in actuation-event not controllable by user"
|
|
108
110
|
)
|
|
109
|
-
return
|
|
111
|
+
return self
|
|
110
112
|
|
|
111
113
|
def get_events(self) -> np.ndarray:
|
|
112
114
|
stop = self.delay + self.count * self.period
|
|
@@ -120,7 +122,7 @@ class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
|
|
|
120
122
|
- reverses pru-gpio (preferred if possible)
|
|
121
123
|
"""
|
|
122
124
|
|
|
123
|
-
events:
|
|
125
|
+
events: Annotated[List[GpioEvent], Field(min_length=1, max_length=1024)]
|
|
124
126
|
|
|
125
127
|
def get_gpios(self) -> set:
|
|
126
128
|
return {_ev.gpio for _ev in self.events}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
from typing import List
|
|
1
2
|
from typing import Optional
|
|
2
3
|
|
|
3
|
-
from pydantic import
|
|
4
|
-
from pydantic import
|
|
5
|
-
from
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
from pydantic import model_validator
|
|
6
|
+
from typing_extensions import Annotated
|
|
6
7
|
|
|
7
8
|
from ..base.content import IdInt
|
|
8
9
|
from ..base.shepherd import ShpModel
|
|
@@ -19,33 +20,38 @@ from .observer_features import PowerTracing
|
|
|
19
20
|
class TargetConfig(ShpModel, title="Target Config"):
|
|
20
21
|
"""Configuration for Target Nodes (DuT)"""
|
|
21
22
|
|
|
22
|
-
target_IDs:
|
|
23
|
-
custom_IDs: Optional[
|
|
23
|
+
target_IDs: Annotated[List[IdInt], Field(min_length=1, max_length=64)]
|
|
24
|
+
custom_IDs: Optional[
|
|
25
|
+
Annotated[List[IdInt16], Field(min_length=1, max_length=64)]
|
|
26
|
+
] = None
|
|
24
27
|
# ⤷ will replace 'const uint16_t SHEPHERD_NODE_ID' in firmware
|
|
25
28
|
# if no custom ID is provided, the original ID of target is used
|
|
26
29
|
|
|
27
30
|
energy_env: EnergyEnvironment # alias: input
|
|
28
31
|
virtual_source: VirtualSourceConfig = VirtualSourceConfig(name="neutral")
|
|
29
|
-
target_delays: Optional[
|
|
32
|
+
target_delays: Optional[
|
|
33
|
+
Annotated[List[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=64)]
|
|
34
|
+
] = None
|
|
30
35
|
# ⤷ individual starting times -> allows to use the same environment
|
|
36
|
+
# TODO: delays not used ATM
|
|
31
37
|
|
|
32
38
|
firmware1: Firmware
|
|
33
39
|
firmware2: Optional[Firmware] = None
|
|
34
40
|
|
|
35
|
-
power_tracing: Optional[PowerTracing]
|
|
36
|
-
gpio_tracing: Optional[GpioTracing]
|
|
37
|
-
gpio_actuation: Optional[GpioActuation]
|
|
41
|
+
power_tracing: Optional[PowerTracing] = None
|
|
42
|
+
gpio_tracing: Optional[GpioTracing] = None
|
|
43
|
+
gpio_actuation: Optional[GpioActuation] = None
|
|
38
44
|
|
|
39
|
-
@
|
|
40
|
-
def post_validation(
|
|
41
|
-
if not
|
|
45
|
+
@model_validator(mode="after")
|
|
46
|
+
def post_validation(self):
|
|
47
|
+
if not self.energy_env.valid:
|
|
42
48
|
raise ValueError(
|
|
43
|
-
f"EnergyEnv '{
|
|
49
|
+
f"EnergyEnv '{self.energy_env.name}' for target must be valid"
|
|
44
50
|
)
|
|
45
|
-
for _id in
|
|
51
|
+
for _id in self.target_IDs:
|
|
46
52
|
target = Target(id=_id)
|
|
47
53
|
for mcu_num in [1, 2]:
|
|
48
|
-
val_fw =
|
|
54
|
+
val_fw = getattr(self, f"firmware{mcu_num}")
|
|
49
55
|
has_fw = val_fw is not None
|
|
50
56
|
tgt_mcu = target[f"mcu{mcu_num}"]
|
|
51
57
|
has_mcu = tgt_mcu is not None
|
|
@@ -65,15 +71,15 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
65
71
|
f"is incompatible (={tgt_mcu.name})"
|
|
66
72
|
)
|
|
67
73
|
|
|
68
|
-
c_ids =
|
|
69
|
-
t_ids =
|
|
74
|
+
c_ids = self.custom_IDs
|
|
75
|
+
t_ids = self.target_IDs
|
|
70
76
|
if c_ids is not None and (len(set(c_ids)) < len(set(t_ids))):
|
|
71
77
|
raise ValueError(
|
|
72
78
|
f"Provided custom IDs {c_ids} not enough "
|
|
73
79
|
f"to cover target range {t_ids}"
|
|
74
80
|
)
|
|
75
81
|
# TODO: if custom ids present, firmware must be ELF
|
|
76
|
-
return
|
|
82
|
+
return self
|
|
77
83
|
|
|
78
84
|
def get_custom_id(self, target_id: int) -> Optional[int]:
|
|
79
85
|
if self.custom_IDs is not None and target_id in self.target_IDs:
|
|
@@ -50,16 +50,18 @@ def prepare_task(
|
|
|
50
50
|
elif isinstance(config, ShpModel):
|
|
51
51
|
shp_wrap = Wrapper(
|
|
52
52
|
datatype=type(config).__name__,
|
|
53
|
-
parameters=config.
|
|
53
|
+
parameters=config.model_dump(),
|
|
54
54
|
)
|
|
55
55
|
else:
|
|
56
56
|
raise ValueError("had unknown input: %s", type(config))
|
|
57
57
|
|
|
58
58
|
if shp_wrap.datatype == TestbedTasks:
|
|
59
59
|
if observer is None:
|
|
60
|
-
|
|
61
|
-
"Task-Set contained TestbedTasks
|
|
60
|
+
logger.debug(
|
|
61
|
+
"Task-Set contained TestbedTasks & no observer was provided "
|
|
62
|
+
"-> will return TB-Tasks"
|
|
62
63
|
)
|
|
64
|
+
return shp_wrap
|
|
63
65
|
tbt = TestbedTasks(**shp_wrap.parameters)
|
|
64
66
|
logger.debug("Loading Testbed-Tasks %s for %s", tbt.name, observer)
|
|
65
67
|
obt = tbt.get_observer_tasks(observer)
|
|
@@ -67,12 +69,12 @@ def prepare_task(
|
|
|
67
69
|
raise ValueError("Observer '%s' is not in TestbedTask-Set", observer)
|
|
68
70
|
shp_wrap = Wrapper(
|
|
69
71
|
datatype=type(obt).__name__,
|
|
70
|
-
parameters=obt.
|
|
72
|
+
parameters=obt.model_dump(),
|
|
71
73
|
)
|
|
72
74
|
return shp_wrap
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
def extract_tasks(shp_wrap: Wrapper) -> List[ShpModel]:
|
|
77
|
+
def extract_tasks(shp_wrap: Wrapper, no_task_sets: bool = True) -> List[ShpModel]:
|
|
76
78
|
""" """
|
|
77
79
|
if shp_wrap.datatype == ObserverTasks:
|
|
78
80
|
obt = ObserverTasks(**shp_wrap.parameters)
|
|
@@ -85,6 +87,12 @@ def extract_tasks(shp_wrap: Wrapper) -> List[ShpModel]:
|
|
|
85
87
|
content = [FirmwareModTask(**shp_wrap.parameters)]
|
|
86
88
|
elif shp_wrap.datatype == ProgrammingTask.__name__:
|
|
87
89
|
content = [ProgrammingTask(**shp_wrap.parameters)]
|
|
90
|
+
elif shp_wrap.datatype == TestbedTasks.__name__:
|
|
91
|
+
if no_task_sets:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"Model in Wrapper was TestbedTasks -> Task-Sets not allowed!"
|
|
94
|
+
)
|
|
95
|
+
content = [TestbedTasks(**shp_wrap.parameters)]
|
|
88
96
|
else:
|
|
89
97
|
raise ValueError("Extractor had unknown task: %s", shp_wrap.datatype)
|
|
90
98
|
|