shepherd-core 2023.11.1__py3-none-any.whl → 2024.4.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 -2
- shepherd_core/data_models/base/calibration.py +3 -1
- shepherd_core/data_models/base/content.py +13 -11
- shepherd_core/data_models/base/shepherd.py +4 -6
- shepherd_core/data_models/content/energy_environment.py +1 -1
- shepherd_core/data_models/content/virtual_harvester.py +1 -1
- shepherd_core/data_models/content/virtual_source.py +31 -53
- shepherd_core/data_models/experiment/experiment.py +13 -18
- shepherd_core/data_models/experiment/observer_features.py +9 -15
- shepherd_core/data_models/experiment/target_config.py +4 -11
- shepherd_core/data_models/task/__init__.py +2 -6
- shepherd_core/data_models/task/emulation.py +7 -14
- shepherd_core/data_models/task/firmware_mod.py +1 -3
- shepherd_core/data_models/task/harvest.py +1 -3
- shepherd_core/data_models/task/observer_tasks.py +1 -1
- shepherd_core/data_models/task/testbed_tasks.py +7 -3
- shepherd_core/data_models/testbed/cape.py +1 -1
- shepherd_core/data_models/testbed/gpio.py +1 -1
- shepherd_core/data_models/testbed/mcu.py +1 -1
- shepherd_core/data_models/testbed/observer.py +7 -23
- shepherd_core/data_models/testbed/target.py +1 -1
- shepherd_core/data_models/testbed/testbed.py +2 -4
- shepherd_core/decoder_waveform/uart.py +9 -26
- shepherd_core/fw_tools/__init__.py +1 -3
- shepherd_core/fw_tools/converter.py +4 -12
- shepherd_core/fw_tools/patcher.py +4 -12
- shepherd_core/fw_tools/validation.py +1 -2
- shepherd_core/inventory/__init__.py +53 -9
- shepherd_core/inventory/system.py +10 -5
- shepherd_core/logger.py +1 -3
- shepherd_core/reader.py +45 -57
- shepherd_core/testbed_client/cache_path.py +15 -0
- shepherd_core/testbed_client/client.py +7 -19
- shepherd_core/testbed_client/fixtures.py +8 -15
- shepherd_core/testbed_client/user_model.py +7 -7
- shepherd_core/vsource/virtual_converter_model.py +5 -15
- shepherd_core/vsource/virtual_harvester_model.py +2 -3
- shepherd_core/vsource/virtual_source_model.py +3 -6
- shepherd_core/writer.py +16 -24
- {shepherd_core-2023.11.1.dist-info → shepherd_core-2024.4.1.dist-info}/METADATA +50 -38
- shepherd_core-2024.4.1.dist-info/RECORD +64 -0
- {shepherd_core-2023.11.1.dist-info → shepherd_core-2024.4.1.dist-info}/WHEEL +1 -1
- {shepherd_core-2023.11.1.dist-info → shepherd_core-2024.4.1.dist-info}/top_level.txt +0 -1
- shepherd_core/data_models/content/_external_fixtures.yaml +0 -394
- shepherd_core/data_models/content/energy_environment_fixture.yaml +0 -50
- shepherd_core/data_models/content/virtual_harvester_fixture.yaml +0 -159
- shepherd_core/data_models/content/virtual_source_fixture.yaml +0 -229
- shepherd_core/data_models/testbed/cape_fixture.yaml +0 -94
- shepherd_core/data_models/testbed/gpio_fixture.yaml +0 -166
- shepherd_core/data_models/testbed/mcu_fixture.yaml +0 -19
- shepherd_core/data_models/testbed/observer_fixture.yaml +0 -220
- shepherd_core/data_models/testbed/target_fixture.yaml +0 -137
- shepherd_core/data_models/testbed/testbed_fixture.yaml +0 -25
- shepherd_core-2023.11.1.dist-info/RECORD +0 -117
- tests/__init__.py +0 -0
- tests/conftest.py +0 -64
- tests/data_models/__init__.py +0 -0
- tests/data_models/conftest.py +0 -14
- tests/data_models/example_cal_data.yaml +0 -31
- tests/data_models/example_cal_data_faulty.yaml +0 -29
- tests/data_models/example_cal_meas.yaml +0 -178
- tests/data_models/example_cal_meas_faulty1.yaml +0 -142
- tests/data_models/example_cal_meas_faulty2.yaml +0 -136
- tests/data_models/example_config_emulator.yaml +0 -41
- tests/data_models/example_config_experiment.yaml +0 -16
- tests/data_models/example_config_experiment_alternative.yaml +0 -14
- tests/data_models/example_config_harvester.yaml +0 -15
- tests/data_models/example_config_testbed.yaml +0 -26
- tests/data_models/example_config_virtsource.yaml +0 -78
- tests/data_models/test_base_models.py +0 -205
- tests/data_models/test_content_fixtures.py +0 -41
- tests/data_models/test_content_models.py +0 -288
- tests/data_models/test_examples.py +0 -48
- tests/data_models/test_experiment_models.py +0 -279
- tests/data_models/test_task_generation.py +0 -52
- tests/data_models/test_task_models.py +0 -131
- tests/data_models/test_testbed_fixtures.py +0 -47
- tests/data_models/test_testbed_models.py +0 -187
- tests/decoder_waveform/__init__.py +0 -0
- tests/decoder_waveform/test_decoder.py +0 -34
- tests/fw_tools/__init__.py +0 -0
- tests/fw_tools/conftest.py +0 -5
- tests/fw_tools/test_converter.py +0 -76
- tests/fw_tools/test_patcher.py +0 -66
- tests/fw_tools/test_validation.py +0 -56
- tests/inventory/__init__.py +0 -0
- tests/inventory/test_inventory.py +0 -22
- tests/test_cal_hw.py +0 -38
- tests/test_examples.py +0 -40
- tests/test_logger.py +0 -15
- tests/test_reader.py +0 -283
- tests/test_writer.py +0 -169
- tests/testbed_client/__init__.py +0 -0
- tests/vsource/__init__.py +0 -0
- tests/vsource/conftest.py +0 -51
- tests/vsource/test_converter.py +0 -165
- tests/vsource/test_harvester.py +0 -79
- tests/vsource/test_z.py +0 -5
- {shepherd_core-2023.11.1.dist-info → shepherd_core-2024.4.1.dist-info}/zip-safe +0 -0
shepherd_core/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ Provides classes for storing and retrieving sampled IV data to/from
|
|
|
4
4
|
HDF5 files.
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
from .data_models.base.calibration import Calc_t
|
|
8
9
|
from .data_models.base.calibration import CalibrationCape
|
|
9
10
|
from .data_models.base.calibration import CalibrationEmulator
|
|
@@ -12,7 +13,7 @@ from .data_models.base.calibration import CalibrationPair
|
|
|
12
13
|
from .data_models.base.calibration import CalibrationSeries
|
|
13
14
|
from .data_models.base.timezone import local_now
|
|
14
15
|
from .data_models.base.timezone import local_tz
|
|
15
|
-
from .data_models.task import Compression
|
|
16
|
+
from .data_models.task.emulation import Compression
|
|
16
17
|
from .inventory import Inventory
|
|
17
18
|
from .logger import get_verbose_level
|
|
18
19
|
from .logger import increase_verbose_level
|
|
@@ -22,7 +23,7 @@ from .testbed_client.client import TestbedClient
|
|
|
22
23
|
from .testbed_client.client import tb_client
|
|
23
24
|
from .writer import Writer
|
|
24
25
|
|
|
25
|
-
__version__ = "
|
|
26
|
+
__version__ = "2024.04.1"
|
|
26
27
|
|
|
27
28
|
__all__ = [
|
|
28
29
|
"Reader",
|
|
@@ -165,7 +165,7 @@ class CapeData(ShpModel):
|
|
|
165
165
|
`See<https://github.com/beagleboard/beaglebone-black/wiki/System-Reference-Manual#824_EEPROM_Data_Format>`_
|
|
166
166
|
"""
|
|
167
167
|
|
|
168
|
-
header: conbytes(max_length=4) = b"\
|
|
168
|
+
header: conbytes(max_length=4) = b"\xaa\x55\x33\xee"
|
|
169
169
|
eeprom_revision: constr(max_length=2) = "A2"
|
|
170
170
|
board_name: constr(max_length=32) = "BeagleBone SHEPHERD2 Cape"
|
|
171
171
|
version: constr(max_length=4) = "24B0"
|
|
@@ -207,6 +207,7 @@ class CalibrationCape(ShpModel):
|
|
|
207
207
|
cape: data can be supplied
|
|
208
208
|
Returns:
|
|
209
209
|
CalibrationCape object with extracted calibration data.
|
|
210
|
+
|
|
210
211
|
"""
|
|
211
212
|
dv = cls().model_dump(include={"harvester", "emulator"})
|
|
212
213
|
lw = list(dict_generator(dv))
|
|
@@ -225,6 +226,7 @@ class CalibrationCape(ShpModel):
|
|
|
225
226
|
Returns
|
|
226
227
|
-------
|
|
227
228
|
Byte string representation of calibration values.
|
|
229
|
+
|
|
228
230
|
"""
|
|
229
231
|
lw = list(dict_generator(self.model_dump(include={"harvester", "emulator"})))
|
|
230
232
|
values = [walk[-1] for walk in lw]
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from typing import Optional
|
|
4
|
+
from typing import Union
|
|
5
|
+
from uuid import uuid4
|
|
4
6
|
|
|
7
|
+
from pydantic import UUID4
|
|
5
8
|
from pydantic import Field
|
|
6
9
|
from pydantic import StringConstraints
|
|
7
10
|
from pydantic import model_validator
|
|
@@ -14,34 +17,34 @@ from .timezone import local_now
|
|
|
14
17
|
# constr -> to_lower=True, max_length=16, regex=r"^[\w]+$"
|
|
15
18
|
# ⤷ Regex = AlphaNum
|
|
16
19
|
IdInt = Annotated[int, Field(ge=0, lt=2**128)]
|
|
17
|
-
NameStr = Annotated[
|
|
18
|
-
str, StringConstraints(max_length=32, pattern=r'^[^<>:;,?"*|\/\\]+$')
|
|
19
|
-
]
|
|
20
|
+
NameStr = Annotated[str, StringConstraints(max_length=32, pattern=r"^[^<>:;,?\"\*|\/\\]+$")]
|
|
20
21
|
# ⤷ Regex = FileSystem-Compatible ASCII
|
|
21
22
|
SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
|
|
22
23
|
# ⤷ Regex = All Printable ASCII-Characters with Space
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def id_default() -> int:
|
|
27
|
+
# note: IdInt has space for 128 bit, so 128/4 = 32 hex-chars
|
|
26
28
|
time_stamp = str(local_now()).encode("utf-8")
|
|
27
|
-
time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-
|
|
29
|
+
time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-32:]
|
|
28
30
|
return int(time_hash, 16)
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class ContentModel(ShpModel):
|
|
32
34
|
# General Properties
|
|
33
|
-
id:
|
|
35
|
+
# id: UUID4 = Field( # TODO: db-migration - temp fix for documentation
|
|
36
|
+
id: Union[UUID4, int] = Field(
|
|
34
37
|
description="Unique ID",
|
|
35
|
-
default_factory=
|
|
38
|
+
default_factory=uuid4,
|
|
36
39
|
)
|
|
37
40
|
name: NameStr
|
|
38
|
-
description: Annotated[
|
|
39
|
-
Optional[SafeStr], Field(description="Required when public")
|
|
40
|
-
] = None
|
|
41
|
+
description: Annotated[Optional[SafeStr], Field(description="Required when public")] = None
|
|
41
42
|
comment: Optional[SafeStr] = None
|
|
42
43
|
created: datetime = Field(default_factory=datetime.now)
|
|
44
|
+
updated_last: datetime = Field(default_factory=datetime.now)
|
|
43
45
|
|
|
44
46
|
# Ownership & Access
|
|
47
|
+
# TODO: remove owner & group, only needed for DB
|
|
45
48
|
owner: NameStr
|
|
46
49
|
group: Annotated[NameStr, Field(description="University or Subgroup")]
|
|
47
50
|
visible2group: bool = False
|
|
@@ -57,7 +60,6 @@ class ContentModel(ShpModel):
|
|
|
57
60
|
is_visible = self.visible2group or self.visible2all
|
|
58
61
|
if is_visible and self.description is None:
|
|
59
62
|
raise ValueError(
|
|
60
|
-
"Public instances require a description "
|
|
61
|
-
"(check visible2*- and description-field)"
|
|
63
|
+
"Public instances require a description (check visible2*- and description-field)"
|
|
62
64
|
)
|
|
63
65
|
return self
|
|
@@ -7,6 +7,7 @@ from typing import Any
|
|
|
7
7
|
from typing import Generator
|
|
8
8
|
from typing import Optional
|
|
9
9
|
from typing import Union
|
|
10
|
+
from uuid import UUID
|
|
10
11
|
|
|
11
12
|
import yaml
|
|
12
13
|
from pydantic import BaseModel
|
|
@@ -27,9 +28,7 @@ def path2str(
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def time2int(dumper: Dumper, data: timedelta) -> ScalarNode:
|
|
30
|
-
return dumper.represent_scalar(
|
|
31
|
-
"tag:yaml.org,2002:int", str(int(data.total_seconds()))
|
|
32
|
-
)
|
|
31
|
+
return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
def generic2str(dumper: Dumper, data: Any) -> ScalarNode:
|
|
@@ -41,6 +40,7 @@ yaml.add_representer(pathlib.WindowsPath, path2str, SafeDumper)
|
|
|
41
40
|
yaml.add_representer(pathlib.Path, path2str, SafeDumper)
|
|
42
41
|
yaml.add_representer(timedelta, time2int, SafeDumper)
|
|
43
42
|
yaml.add_representer(IPv4Address, generic2str, SafeDumper)
|
|
43
|
+
yaml.add_representer(UUID, generic2str, SafeDumper)
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class ShpModel(BaseModel):
|
|
@@ -109,9 +109,7 @@ class ShpModel(BaseModel):
|
|
|
109
109
|
def schema_to_file(cls, path: Union[str, Path]) -> None:
|
|
110
110
|
"""Store schema to yaml (for frontend-generators)"""
|
|
111
111
|
model_dict = cls.model_json_schema()
|
|
112
|
-
model_yaml = yaml.safe_dump(
|
|
113
|
-
model_dict, default_flow_style=False, sort_keys=False
|
|
114
|
-
)
|
|
112
|
+
model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
|
|
115
113
|
with Path(path).resolve().with_suffix(".yaml").open("w") as f:
|
|
116
114
|
f.write(model_yaml)
|
|
117
115
|
|
|
@@ -76,7 +76,7 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
76
76
|
logger.debug("VHrv-Inheritances: %s", chain)
|
|
77
77
|
|
|
78
78
|
# post corrections -> should be in separate validator
|
|
79
|
-
cal = CalibrationHarvester() #
|
|
79
|
+
cal = CalibrationHarvester() # TODO: as argument?
|
|
80
80
|
c_limit = values.get("current_limit_uA", 50_000) # cls.current_limit_uA)
|
|
81
81
|
values["current_limit_uA"] = max(10**6 * cal.adc_C_Hrv.raw_to_si(4), c_limit)
|
|
82
82
|
|
|
@@ -25,12 +25,13 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
25
25
|
for supplying the Target Node during the experiment.
|
|
26
26
|
If not already done, the energy will be harvested and then converted.
|
|
27
27
|
The converter-stage is software defined and offers:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
TODO: I,V,R should be in regular unit (V, A, Ohm)
|
|
28
|
+
- buck-boost-combinations,
|
|
29
|
+
- a simple diode + resistor and
|
|
30
|
+
- an intermediate buffer capacitor.
|
|
32
31
|
"""
|
|
33
32
|
|
|
33
|
+
# TODO: I,V,R should be in regular unit (V, A, Ohm)
|
|
34
|
+
|
|
34
35
|
# General Metadata & Ownership -> ContentModel
|
|
35
36
|
|
|
36
37
|
enable_boost: bool = False
|
|
@@ -123,14 +124,17 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
123
124
|
only has simpler formula, second enabling when V_Cap >= V_out
|
|
124
125
|
|
|
125
126
|
Math behind this calculation:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
convert
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
|
|
128
|
+
- Energy-Change Storage Cap -> E_new = E_old - E_output
|
|
129
|
+
- with Energy of a Cap -> E_x = C_x * V_x^2 / 2
|
|
130
|
+
- combine formulas -> C_store * V_store_new^2 / 2 =
|
|
131
|
+
C_store * V_store_old^2 / 2 - C_out * V_out^2 / 2
|
|
132
|
+
- convert formula to V_new -> V_store_new^2 =
|
|
133
|
+
V_store_old^2 - (C_out / C_store) * V_out^2
|
|
134
|
+
- convert into dV -> dV = V_store_new - V_store_old
|
|
135
|
+
- in case of V_cap = V_out -> dV = V_store_old * (sqrt(1 - C_out / C_store) - 1)
|
|
136
|
+
|
|
137
|
+
Note: dV values will be reversed (negated), because dV is always negative (Voltage drop)
|
|
134
138
|
"""
|
|
135
139
|
values = {}
|
|
136
140
|
if self.C_intermediate_uF > 0 and self.C_output_uF > 0:
|
|
@@ -153,15 +157,9 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
153
157
|
dV_output_imed_low_mV = 0
|
|
154
158
|
|
|
155
159
|
# protect from complex solutions (non valid input combinations)
|
|
156
|
-
if not (
|
|
157
|
-
isinstance(dV_output_en_thrs_mV, (int, float))
|
|
158
|
-
and (dV_output_en_thrs_mV >= 0)
|
|
159
|
-
):
|
|
160
|
+
if not (isinstance(dV_output_en_thrs_mV, (int, float)) and (dV_output_en_thrs_mV >= 0)):
|
|
160
161
|
dV_output_en_thrs_mV = 0
|
|
161
|
-
if not (
|
|
162
|
-
isinstance(dV_output_imed_low_mV, (int, float))
|
|
163
|
-
and (dV_output_imed_low_mV >= 0)
|
|
164
|
-
):
|
|
162
|
+
if not (isinstance(dV_output_imed_low_mV, (int, float)) and (dV_output_imed_low_mV >= 0)):
|
|
165
163
|
logger.warning("VSrc: C_output shouldn't be larger than C_intermediate")
|
|
166
164
|
dV_output_imed_low_mV = 0
|
|
167
165
|
|
|
@@ -171,9 +169,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
171
169
|
|
|
172
170
|
if self.V_intermediate_enable_threshold_mV > V_pre_output_mV:
|
|
173
171
|
values["dV_enable_output_mV"] = dV_output_en_thrs_mV
|
|
174
|
-
values[
|
|
175
|
-
"V_enable_output_threshold_mV"
|
|
176
|
-
] = self.V_intermediate_enable_threshold_mV
|
|
172
|
+
values["V_enable_output_threshold_mV"] = self.V_intermediate_enable_threshold_mV
|
|
177
173
|
|
|
178
174
|
else:
|
|
179
175
|
values["dV_enable_output_mV"] = dV_output_imed_low_mV
|
|
@@ -182,26 +178,21 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
182
178
|
)
|
|
183
179
|
|
|
184
180
|
if self.V_intermediate_disable_threshold_mV > V_pre_output_mV:
|
|
185
|
-
values[
|
|
186
|
-
"V_disable_output_threshold_mV"
|
|
187
|
-
] = self.V_intermediate_disable_threshold_mV
|
|
181
|
+
values["V_disable_output_threshold_mV"] = self.V_intermediate_disable_threshold_mV
|
|
188
182
|
else:
|
|
189
183
|
values["V_disable_output_threshold_mV"] = V_pre_output_mV
|
|
190
184
|
|
|
191
185
|
else:
|
|
192
186
|
values["dV_enable_output_mV"] = dV_output_en_thrs_mV
|
|
193
|
-
values[
|
|
194
|
-
|
|
195
|
-
] = self.V_intermediate_enable_threshold_mV
|
|
196
|
-
values[
|
|
197
|
-
"V_disable_output_threshold_mV"
|
|
198
|
-
] = self.V_intermediate_disable_threshold_mV
|
|
187
|
+
values["V_enable_output_threshold_mV"] = self.V_intermediate_enable_threshold_mV
|
|
188
|
+
values["V_disable_output_threshold_mV"] = self.V_intermediate_disable_threshold_mV
|
|
199
189
|
return values
|
|
200
190
|
|
|
201
191
|
def calc_converter_mode(self, *, log_intermediate_node: bool) -> int:
|
|
202
192
|
"""Assembles bitmask from discrete values
|
|
193
|
+
|
|
203
194
|
log_intermediate_node: record / log virtual intermediate (cap-)voltage and
|
|
204
|
-
|
|
195
|
+
-current (out) instead of output-voltage and -current
|
|
205
196
|
"""
|
|
206
197
|
enable_storage = self.C_intermediate_uF > 0
|
|
207
198
|
enable_boost = self.enable_boost and enable_storage
|
|
@@ -286,9 +277,7 @@ class ConverterPRUConfig(ShpModel):
|
|
|
286
277
|
states = data.calc_internal_states()
|
|
287
278
|
return cls(
|
|
288
279
|
# General
|
|
289
|
-
converter_mode=data.calc_converter_mode(
|
|
290
|
-
log_intermediate_node=log_intermediate_node
|
|
291
|
-
),
|
|
280
|
+
converter_mode=data.calc_converter_mode(log_intermediate_node=log_intermediate_node),
|
|
292
281
|
interval_startup_delay_drain_n=round(
|
|
293
282
|
data.interval_startup_delay_drain_ms * samplerate_sps_default * 1e-3
|
|
294
283
|
),
|
|
@@ -299,26 +288,16 @@ class ConverterPRUConfig(ShpModel):
|
|
|
299
288
|
Constant_us_per_nF_n28=data.calc_cap_constant_us_per_nF_n28(),
|
|
300
289
|
V_intermediate_init_uV=round(data.V_intermediate_init_mV * 1e3),
|
|
301
290
|
I_intermediate_leak_nA=round(data.I_intermediate_leak_nA),
|
|
302
|
-
V_enable_output_threshold_uV=round(
|
|
303
|
-
|
|
304
|
-
),
|
|
305
|
-
V_disable_output_threshold_uV=round(
|
|
306
|
-
states["V_disable_output_threshold_mV"] * 1e3
|
|
307
|
-
),
|
|
291
|
+
V_enable_output_threshold_uV=round(states["V_enable_output_threshold_mV"] * 1e3),
|
|
292
|
+
V_disable_output_threshold_uV=round(states["V_disable_output_threshold_mV"] * 1e3),
|
|
308
293
|
dV_enable_output_uV=round(states["dV_enable_output_mV"] * 1e3),
|
|
309
294
|
interval_check_thresholds_n=round(
|
|
310
295
|
data.interval_check_thresholds_ms * samplerate_sps_default * 1e-3
|
|
311
296
|
),
|
|
312
|
-
V_pwr_good_enable_threshold_uV=round(
|
|
313
|
-
|
|
314
|
-
),
|
|
315
|
-
V_pwr_good_disable_threshold_uV=round(
|
|
316
|
-
data.V_pwr_good_disable_threshold_mV * 1e3
|
|
317
|
-
),
|
|
297
|
+
V_pwr_good_enable_threshold_uV=round(data.V_pwr_good_enable_threshold_mV * 1e3),
|
|
298
|
+
V_pwr_good_disable_threshold_uV=round(data.V_pwr_good_disable_threshold_mV * 1e3),
|
|
318
299
|
immediate_pwr_good_signal=data.immediate_pwr_good_signal,
|
|
319
|
-
V_output_log_gpio_threshold_uV=round(
|
|
320
|
-
data.V_output_log_gpio_threshold_mV * 1e3
|
|
321
|
-
),
|
|
300
|
+
V_output_log_gpio_threshold_uV=round(data.V_output_log_gpio_threshold_mV * 1e3),
|
|
322
301
|
# Boost-Converter
|
|
323
302
|
V_input_boost_threshold_uV=round(data.V_input_boost_threshold_mV * 1e3),
|
|
324
303
|
V_intermediate_max_uV=round(data.V_intermediate_max_mV * 1e3),
|
|
@@ -330,8 +309,7 @@ class ConverterPRUConfig(ShpModel):
|
|
|
330
309
|
LUT_input_I_min_log2_nA=data.LUT_input_I_min_log2_nA,
|
|
331
310
|
LUT_output_I_min_log2_nA=data.LUT_output_I_min_log2_nA,
|
|
332
311
|
LUT_inp_efficiency_n8=[
|
|
333
|
-
[min(255, round(256 * ival)) for ival in il]
|
|
334
|
-
for il in data.LUT_input_efficiency
|
|
312
|
+
[min(255, round(256 * ival)) for ival in il] for il in data.LUT_input_efficiency
|
|
335
313
|
],
|
|
336
314
|
LUT_out_inv_efficiency_n4=[
|
|
337
315
|
min((2**14), round((2**4) / value)) if (value > 0) else int(2**14)
|
|
@@ -2,8 +2,10 @@ from datetime import datetime
|
|
|
2
2
|
from datetime import timedelta
|
|
3
3
|
from typing import List
|
|
4
4
|
from typing import Optional
|
|
5
|
+
from typing import Union
|
|
6
|
+
from uuid import uuid4
|
|
5
7
|
|
|
6
|
-
from pydantic import
|
|
8
|
+
from pydantic import UUID4
|
|
7
9
|
from pydantic import Field
|
|
8
10
|
from pydantic import model_validator
|
|
9
11
|
from typing_extensions import Annotated
|
|
@@ -12,7 +14,6 @@ from typing_extensions import Self
|
|
|
12
14
|
from ..base.content import IdInt
|
|
13
15
|
from ..base.content import NameStr
|
|
14
16
|
from ..base.content import SafeStr
|
|
15
|
-
from ..base.content import id_default
|
|
16
17
|
from ..base.shepherd import ShpModel
|
|
17
18
|
from ..testbed.target import Target
|
|
18
19
|
from ..testbed.testbed import Testbed
|
|
@@ -26,11 +27,10 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
26
27
|
"""
|
|
27
28
|
|
|
28
29
|
# General Properties
|
|
29
|
-
id:
|
|
30
|
-
|
|
31
|
-
default_factory=id_default,
|
|
32
|
-
)
|
|
30
|
+
# id: UUID4 ... # TODO db-migration - temp fix for documentation
|
|
31
|
+
id: Union[UUID4, int] = Field(default_factory=uuid4)
|
|
33
32
|
# ⤷ TODO: automatic ID is problematic for identification by hash
|
|
33
|
+
|
|
34
34
|
name: NameStr
|
|
35
35
|
description: Annotated[
|
|
36
36
|
Optional[SafeStr], Field(description="Required for public instances")
|
|
@@ -38,12 +38,12 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
38
38
|
comment: Optional[SafeStr] = None
|
|
39
39
|
created: datetime = Field(default_factory=datetime.now)
|
|
40
40
|
|
|
41
|
-
# Ownership & Access
|
|
42
|
-
owner_id: Optional[IdInt] =
|
|
41
|
+
# Ownership & Access
|
|
42
|
+
owner_id: Optional[IdInt] = None
|
|
43
43
|
|
|
44
44
|
# feedback
|
|
45
|
-
email_results:
|
|
46
|
-
|
|
45
|
+
email_results: bool = False
|
|
46
|
+
|
|
47
47
|
sys_logging: SystemLogging = SystemLogging(dmesg=True, ptp=True, shepherd=True)
|
|
48
48
|
|
|
49
49
|
# schedule
|
|
@@ -80,9 +80,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
80
80
|
if len(target_ids) > len(set(target_ids)):
|
|
81
81
|
raise ValueError("Target-ID used more than once in Experiment!")
|
|
82
82
|
if len(target_ids) > len(set(custom_ids)):
|
|
83
|
-
raise ValueError(
|
|
84
|
-
"Custom Target-ID are faulty (some form of id-collisions)!"
|
|
85
|
-
)
|
|
83
|
+
raise ValueError("Custom Target-ID are faulty (some form of id-collisions)!")
|
|
86
84
|
|
|
87
85
|
@staticmethod
|
|
88
86
|
def validate_observers(configs: List[TargetConfig]) -> None:
|
|
@@ -92,8 +90,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
92
90
|
obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
|
|
93
91
|
if len(target_ids) > len(set(obs_ids)):
|
|
94
92
|
raise ValueError(
|
|
95
|
-
"Observer used more than once in Experiment "
|
|
96
|
-
"-> only 1 target per observer!"
|
|
93
|
+
"Observer used more than once in Experiment -> only 1 target per observer!"
|
|
97
94
|
)
|
|
98
95
|
|
|
99
96
|
def get_target_ids(self) -> list:
|
|
@@ -104,6 +101,4 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
104
101
|
if target_id in _config.target_IDs:
|
|
105
102
|
return _config
|
|
106
103
|
# gets already caught in target_config - but keep:
|
|
107
|
-
raise ValueError(
|
|
108
|
-
f"Target-ID {target_id} was not found in Experiment '{self.name}'"
|
|
109
|
-
)
|
|
104
|
+
raise ValueError(f"Target-ID {target_id} was not found in Experiment '{self.name}'")
|
|
@@ -43,9 +43,7 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
43
43
|
|
|
44
44
|
discard_all = self.discard_current and self.discard_voltage
|
|
45
45
|
if not self.calculate_power and discard_all:
|
|
46
|
-
raise ValueError(
|
|
47
|
-
"Error in config -> tracing enabled, but output gets discarded"
|
|
48
|
-
)
|
|
46
|
+
raise ValueError("Error in config -> tracing enabled, but output gets discarded")
|
|
49
47
|
if self.calculate_power:
|
|
50
48
|
raise ValueError("postprocessing not implemented ATM")
|
|
51
49
|
return self
|
|
@@ -59,9 +57,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
59
57
|
# initial recording
|
|
60
58
|
mask: Annotated[int, Field(ge=0, lt=2**10)] = 0b11_1111_1111 # all
|
|
61
59
|
# ⤷ TODO: custom mask not implemented in PRU, ATM
|
|
62
|
-
gpios: Optional[
|
|
63
|
-
Annotated[List[GPIO], Field(min_length=1, max_length=10)]
|
|
64
|
-
] = None # = all
|
|
60
|
+
gpios: Optional[Annotated[List[GPIO], Field(min_length=1, max_length=10)]] = None # = all
|
|
65
61
|
# ⤷ TODO: list of GPIO to build mask, one of both should be internal / computed field
|
|
66
62
|
|
|
67
63
|
# time
|
|
@@ -70,7 +66,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
70
66
|
|
|
71
67
|
# post-processing,
|
|
72
68
|
uart_decode: bool = False
|
|
73
|
-
#
|
|
69
|
+
# TODO: quickfix - uart-log currently done online in userspace
|
|
74
70
|
# NOTE: gpio-tracing currently shows rather big - but rare - "blind" windows (~1-4us)
|
|
75
71
|
uart_pin: GPIO = GPIO(name="GPIO8")
|
|
76
72
|
uart_baudrate: Annotated[int, Field(ge=2_400, le=921_600)] = 115_200
|
|
@@ -108,9 +104,7 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
|
|
|
108
104
|
@model_validator(mode="after")
|
|
109
105
|
def post_validation(self) -> Self:
|
|
110
106
|
if not self.gpio.user_controllable():
|
|
111
|
-
raise ValueError(
|
|
112
|
-
f"GPIO '{self.gpio.name}' in actuation-event not controllable by user"
|
|
113
|
-
)
|
|
107
|
+
raise ValueError(f"GPIO '{self.gpio.name}' in actuation-event not controllable by user")
|
|
114
108
|
return self
|
|
115
109
|
|
|
116
110
|
def get_events(self) -> np.ndarray:
|
|
@@ -119,11 +113,10 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
|
|
|
119
113
|
|
|
120
114
|
|
|
121
115
|
class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
|
|
122
|
-
"""Configuration for a GPIO-Actuation-Sequence
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"""
|
|
116
|
+
"""Configuration for a GPIO-Actuation-Sequence"""
|
|
117
|
+
|
|
118
|
+
# TODO: not implemented ATM - decide if pru control sys-gpio or
|
|
119
|
+
# TODO: not implemented ATM - reverses pru-gpio (preferred if possible)
|
|
127
120
|
|
|
128
121
|
events: Annotated[List[GpioEvent], Field(min_length=1, max_length=1024)]
|
|
129
122
|
|
|
@@ -138,6 +131,7 @@ class SystemLogging(ShpModel, title="Config for System-Logging"):
|
|
|
138
131
|
ptp: bool = True
|
|
139
132
|
shepherd: bool = True
|
|
140
133
|
# TODO: rename to kernel, timesync, sheep
|
|
134
|
+
# TODO: add utilization as option
|
|
141
135
|
|
|
142
136
|
|
|
143
137
|
# TODO: some more interaction would be good
|
|
@@ -22,18 +22,14 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
22
22
|
"""Configuration for Target Nodes (DuT)"""
|
|
23
23
|
|
|
24
24
|
target_IDs: Annotated[List[IdInt], Field(min_length=1, max_length=128)]
|
|
25
|
-
custom_IDs: Optional[
|
|
26
|
-
Annotated[List[IdInt16], Field(min_length=1, max_length=128)]
|
|
27
|
-
] = None
|
|
25
|
+
custom_IDs: Optional[Annotated[List[IdInt16], Field(min_length=1, max_length=128)]] = None
|
|
28
26
|
# ⤷ will replace 'const uint16_t SHEPHERD_NODE_ID' in firmware
|
|
29
27
|
# if no custom ID is provided, the original ID of target is used
|
|
30
28
|
|
|
31
29
|
energy_env: EnergyEnvironment # alias: input
|
|
32
30
|
virtual_source: VirtualSourceConfig = VirtualSourceConfig(name="neutral")
|
|
33
31
|
target_delays: Optional[
|
|
34
|
-
Annotated[
|
|
35
|
-
List[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)
|
|
36
|
-
]
|
|
32
|
+
Annotated[List[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)]
|
|
37
33
|
] = None
|
|
38
34
|
# ⤷ individual starting times -> allows to use the same environment
|
|
39
35
|
# TODO: delays not used ATM
|
|
@@ -48,9 +44,7 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
48
44
|
@model_validator(mode="after")
|
|
49
45
|
def post_validation(self) -> Self:
|
|
50
46
|
if not self.energy_env.valid:
|
|
51
|
-
raise ValueError(
|
|
52
|
-
f"EnergyEnv '{self.energy_env.name}' for target must be valid"
|
|
53
|
-
)
|
|
47
|
+
raise ValueError(f"EnergyEnv '{self.energy_env.name}' for target must be valid")
|
|
54
48
|
for _id in self.target_IDs:
|
|
55
49
|
target = Target(id=_id)
|
|
56
50
|
for mcu_num in [1, 2]:
|
|
@@ -78,8 +72,7 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
78
72
|
t_ids = self.target_IDs
|
|
79
73
|
if c_ids is not None and (len(set(c_ids)) < len(set(t_ids))):
|
|
80
74
|
raise ValueError(
|
|
81
|
-
f"Provided custom IDs {c_ids} not enough "
|
|
82
|
-
f"to cover target range {t_ids}"
|
|
75
|
+
f"Provided custom IDs {c_ids} not enough to cover target range {t_ids}"
|
|
83
76
|
)
|
|
84
77
|
# TODO: if custom ids present, firmware must be ELF
|
|
85
78
|
return self
|
|
@@ -32,9 +32,7 @@ __all__ = [
|
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def prepare_task(
|
|
36
|
-
config: Union[ShpModel, Path, str], observer: Optional[str] = None
|
|
37
|
-
) -> Wrapper:
|
|
35
|
+
def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = None) -> Wrapper:
|
|
38
36
|
"""- Opens file (from Path or str of Path)
|
|
39
37
|
- wraps task-model
|
|
40
38
|
- and if it's an TestbedTasks it will extract the correct ObserverTask
|
|
@@ -88,9 +86,7 @@ def extract_tasks(shp_wrap: Wrapper, *, no_task_sets: bool = True) -> List[ShpMo
|
|
|
88
86
|
content = [ProgrammingTask(**shp_wrap.parameters)]
|
|
89
87
|
elif shp_wrap.datatype == TestbedTasks.__name__:
|
|
90
88
|
if no_task_sets:
|
|
91
|
-
raise ValueError(
|
|
92
|
-
"Model in Wrapper was TestbedTasks -> Task-Sets not allowed!"
|
|
93
|
-
)
|
|
89
|
+
raise ValueError("Model in Wrapper was TestbedTasks -> Task-Sets not allowed!")
|
|
94
90
|
content = [TestbedTasks(**shp_wrap.parameters)]
|
|
95
91
|
else:
|
|
96
92
|
raise ValueError("Extractor had unknown task: %s", shp_wrap.datatype)
|
|
@@ -29,8 +29,9 @@ class Compression(str, Enum):
|
|
|
29
29
|
lzf = "lzf" # not native hdf5
|
|
30
30
|
gzip1 = 1 # higher compr & load
|
|
31
31
|
gzip = 1
|
|
32
|
-
default =
|
|
32
|
+
default = "lzf"
|
|
33
33
|
null = None
|
|
34
|
+
# NOTE: changed to lzf as BBB needs every straw it can get
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
compressions_allowed: list = [None, "lzf", 1]
|
|
@@ -52,8 +53,7 @@ class EmulationTask(ShpModel):
|
|
|
52
53
|
force_overwrite: bool = False
|
|
53
54
|
# ⤷ Overwrite existing file
|
|
54
55
|
output_compression: Optional[Compression] = Compression.default
|
|
55
|
-
# ⤷ should be 1 (level 1
|
|
56
|
-
|
|
56
|
+
# ⤷ should be lzf, 1 (gzip level 1) or None (order of recommendation)
|
|
57
57
|
time_start: Optional[datetime] = None
|
|
58
58
|
# timestamp or unix epoch time, None = ASAP
|
|
59
59
|
duration: Optional[timedelta] = None
|
|
@@ -98,9 +98,7 @@ class EmulationTask(ShpModel):
|
|
|
98
98
|
# convert & add local timezone-data
|
|
99
99
|
has_time = values.get("time_start") is not None
|
|
100
100
|
if has_time and isinstance(values["time_start"], (int, float)):
|
|
101
|
-
values["time_start"] = datetime.fromtimestamp(
|
|
102
|
-
values["time_start"], tz=local_tz()
|
|
103
|
-
)
|
|
101
|
+
values["time_start"] = datetime.fromtimestamp(values["time_start"], tz=local_tz())
|
|
104
102
|
if has_time and isinstance(values["time_start"], str):
|
|
105
103
|
values["time_start"] = datetime.fromisoformat(values["time_start"])
|
|
106
104
|
if has_time and values["time_start"].tzinfo is None:
|
|
@@ -123,18 +121,14 @@ class EmulationTask(ShpModel):
|
|
|
123
121
|
"main",
|
|
124
122
|
"buffer",
|
|
125
123
|
}:
|
|
126
|
-
raise ValueError(
|
|
127
|
-
"Voltage Aux must be in float (0 - 4.5) or string 'main' / 'mid'."
|
|
128
|
-
)
|
|
124
|
+
raise ValueError("Voltage Aux must be in float (0 - 4.5) or string 'main' / 'mid'.")
|
|
129
125
|
if self.gpio_actuation is not None:
|
|
130
126
|
raise ValueError("GPIO Actuation not yet implemented!")
|
|
131
127
|
return self
|
|
132
128
|
|
|
133
129
|
@classmethod
|
|
134
130
|
@validate_call
|
|
135
|
-
def from_xp(
|
|
136
|
-
cls, xp: Experiment, tb: Testbed, tgt_id: IdInt, root_path: Path
|
|
137
|
-
) -> Self:
|
|
131
|
+
def from_xp(cls, xp: Experiment, tb: Testbed, tgt_id: IdInt, root_path: Path) -> Self:
|
|
138
132
|
obs = tb.get_observer(tgt_id)
|
|
139
133
|
tgt_cfg = xp.get_target_config(tgt_id)
|
|
140
134
|
|
|
@@ -144,8 +138,7 @@ class EmulationTask(ShpModel):
|
|
|
144
138
|
time_start=copy.copy(xp.time_start),
|
|
145
139
|
duration=xp.duration,
|
|
146
140
|
abort_on_error=xp.abort_on_error,
|
|
147
|
-
enable_io=(tgt_cfg.gpio_tracing is not None)
|
|
148
|
-
or (tgt_cfg.gpio_actuation is not None),
|
|
141
|
+
enable_io=(tgt_cfg.gpio_tracing is not None) or (tgt_cfg.gpio_actuation is not None),
|
|
149
142
|
io_port=obs.get_target_port(tgt_id),
|
|
150
143
|
pwr_port=obs.get_target_port(tgt_id),
|
|
151
144
|
virtual_source=tgt_cfg.virtual_source,
|
|
@@ -40,9 +40,7 @@ class FirmwareModTask(ShpModel):
|
|
|
40
40
|
FirmwareDType.base64_hex,
|
|
41
41
|
FirmwareDType.path_hex,
|
|
42
42
|
}:
|
|
43
|
-
logger.warning(
|
|
44
|
-
"Firmware is scheduled to get custom-ID but is not in elf-format"
|
|
45
|
-
)
|
|
43
|
+
logger.warning("Firmware is scheduled to get custom-ID but is not in elf-format")
|
|
46
44
|
return self
|
|
47
45
|
|
|
48
46
|
@classmethod
|
|
@@ -60,9 +60,7 @@ class HarvestTask(ShpModel):
|
|
|
60
60
|
# convert & add local timezone-data, TODO: used twice, refactor
|
|
61
61
|
has_time = values.get("time_start") is not None
|
|
62
62
|
if has_time and isinstance(values["time_start"], (int, float)):
|
|
63
|
-
values["time_start"] = datetime.fromtimestamp(
|
|
64
|
-
values["time_start"], tz=local_tz()
|
|
65
|
-
)
|
|
63
|
+
values["time_start"] = datetime.fromtimestamp(values["time_start"], tz=local_tz())
|
|
66
64
|
if has_time and isinstance(values["time_start"], str):
|
|
67
65
|
values["time_start"] = datetime.fromisoformat(values["time_start"])
|
|
68
66
|
if has_time and values["time_start"].tzinfo is None:
|
|
@@ -21,7 +21,7 @@ class ObserverTasks(ShpModel):
|
|
|
21
21
|
"""Collection of tasks for selected observer included in experiment"""
|
|
22
22
|
|
|
23
23
|
observer: NameStr
|
|
24
|
-
owner_id: IdInt
|
|
24
|
+
owner_id: Optional[IdInt] # TODO: set to optional for now, shouldn't be
|
|
25
25
|
|
|
26
26
|
# PRE PROCESS
|
|
27
27
|
time_prep: datetime # TODO: should be optional
|