shepherd-core 2025.6.2__py3-none-any.whl → 2025.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- shepherd_core/data_models/base/cal_measurement.py +4 -5
- shepherd_core/data_models/base/calibration.py +8 -10
- shepherd_core/data_models/base/content.py +2 -3
- shepherd_core/data_models/base/shepherd.py +31 -15
- shepherd_core/data_models/base/wrapper.py +3 -4
- shepherd_core/data_models/content/energy_environment.py +4 -5
- shepherd_core/data_models/content/firmware.py +3 -5
- shepherd_core/data_models/content/virtual_harvester.py +5 -6
- shepherd_core/data_models/experiment/experiment.py +9 -17
- shepherd_core/data_models/experiment/observer_features.py +15 -37
- shepherd_core/data_models/experiment/target_config.py +10 -11
- shepherd_core/data_models/task/__init__.py +3 -4
- shepherd_core/data_models/task/emulation.py +10 -14
- shepherd_core/data_models/task/firmware_mod.py +2 -4
- shepherd_core/data_models/task/harvest.py +4 -7
- shepherd_core/data_models/task/observer_tasks.py +7 -8
- shepherd_core/data_models/task/programming.py +1 -2
- shepherd_core/data_models/task/testbed_tasks.py +2 -9
- shepherd_core/data_models/testbed/cape.py +3 -5
- shepherd_core/data_models/testbed/gpio.py +7 -8
- shepherd_core/data_models/testbed/mcu.py +1 -2
- shepherd_core/data_models/testbed/observer.py +5 -6
- shepherd_core/data_models/testbed/target.py +4 -6
- shepherd_core/data_models/testbed/testbed.py +2 -3
- shepherd_core/decoder_waveform/uart.py +11 -13
- shepherd_core/fw_tools/converter.py +1 -2
- shepherd_core/fw_tools/converter_elf.py +1 -2
- shepherd_core/fw_tools/patcher.py +5 -6
- shepherd_core/inventory/python.py +8 -9
- shepherd_core/inventory/system.py +1 -2
- shepherd_core/inventory/target.py +1 -2
- shepherd_core/logger.py +1 -2
- shepherd_core/reader.py +18 -23
- shepherd_core/testbed_client/client_abc_fix.py +2 -7
- shepherd_core/testbed_client/client_web.py +5 -9
- shepherd_core/testbed_client/fixtures.py +3 -5
- shepherd_core/testbed_client/user_model.py +4 -5
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/virtual_converter_model.py +1 -2
- shepherd_core/vsource/virtual_harvester_simulation.py +1 -2
- shepherd_core/vsource/virtual_source_model.py +3 -5
- shepherd_core/vsource/virtual_source_simulation.py +1 -2
- shepherd_core/writer.py +12 -14
- {shepherd_core-2025.6.2.dist-info → shepherd_core-2025.6.4.dist-info}/METADATA +2 -3
- shepherd_core-2025.6.4.dist-info/RECORD +83 -0
- shepherd_core-2025.6.2.dist-info/RECORD +0 -83
- {shepherd_core-2025.6.2.dist-info → shepherd_core-2025.6.4.dist-info}/WHEEL +0 -0
- {shepherd_core-2025.6.2.dist-info → shepherd_core-2025.6.4.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.6.2.dist-info → shepherd_core-2025.6.4.dist-info}/zip-safe +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Models for the process of calibration a device by measurements."""
|
|
2
2
|
|
|
3
3
|
from typing import Annotated
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
import numpy as np
|
|
7
6
|
from pydantic import Field
|
|
@@ -91,11 +90,11 @@ class CalMeasurementEmulator(ShpModel):
|
|
|
91
90
|
class CalMeasurementCape(ShpModel):
|
|
92
91
|
"""Container for the values of the calibration-measurement."""
|
|
93
92
|
|
|
94
|
-
cape:
|
|
95
|
-
host:
|
|
93
|
+
cape: CapeData | None = None
|
|
94
|
+
host: str | None = None
|
|
96
95
|
|
|
97
|
-
harvester:
|
|
98
|
-
emulator:
|
|
96
|
+
harvester: CalMeasurementHarvester | None = None
|
|
97
|
+
emulator: CalMeasurementEmulator | None = None
|
|
99
98
|
|
|
100
99
|
def to_cal(self) -> CalibrationCape:
|
|
101
100
|
dv = self.model_dump()
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"""Models for the calibration-data to convert between raw & SI-Values."""
|
|
2
2
|
|
|
3
3
|
import struct
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from collections.abc import Generator
|
|
5
6
|
from collections.abc import Mapping
|
|
6
7
|
from collections.abc import Sequence
|
|
7
|
-
from typing import Callable
|
|
8
|
-
from typing import Optional
|
|
9
8
|
from typing import TypeVar
|
|
10
|
-
from typing import Union
|
|
11
9
|
|
|
12
10
|
import numpy as np
|
|
13
11
|
from numpy.typing import NDArray
|
|
@@ -29,7 +27,7 @@ Calc_t = TypeVar("Calc_t", NDArray[np.float64], float)
|
|
|
29
27
|
|
|
30
28
|
|
|
31
29
|
def dict_generator(
|
|
32
|
-
in_dict:
|
|
30
|
+
in_dict: Mapping | Sequence, pre: list | None = None
|
|
33
31
|
) -> Generator[list, None, None]:
|
|
34
32
|
"""Recursive helper-function to generate a 1D-List(or not?).
|
|
35
33
|
|
|
@@ -54,7 +52,7 @@ class CalibrationPair(ShpModel):
|
|
|
54
52
|
|
|
55
53
|
gain: PositiveFloat
|
|
56
54
|
offset: float = 0
|
|
57
|
-
unit:
|
|
55
|
+
unit: str | None = None # TODO: add units when used
|
|
58
56
|
|
|
59
57
|
def raw_to_si(self, values_raw: Calc_t, *, allow_negative: bool = True) -> Calc_t:
|
|
60
58
|
"""Convert between physical units and raw unsigned integers."""
|
|
@@ -81,7 +79,7 @@ class CalibrationPair(ShpModel):
|
|
|
81
79
|
return values_raw
|
|
82
80
|
|
|
83
81
|
@classmethod
|
|
84
|
-
def from_fn(cls, fn: Callable, unit:
|
|
82
|
+
def from_fn(cls, fn: Callable, unit: str | None = None) -> Self:
|
|
85
83
|
"""Probe linear function to determine scaling values."""
|
|
86
84
|
offset = fn(0, limited=False)
|
|
87
85
|
gain_inv = fn(1.0, limited=False) - offset
|
|
@@ -217,14 +215,14 @@ class CalibrationCape(ShpModel):
|
|
|
217
215
|
YAML: .to_file() and .from_file() already in ShpModel
|
|
218
216
|
"""
|
|
219
217
|
|
|
220
|
-
cape:
|
|
221
|
-
host:
|
|
218
|
+
cape: CapeData | None = None
|
|
219
|
+
host: str | None = None
|
|
222
220
|
|
|
223
221
|
harvester: CalibrationHarvester = CalibrationHarvester()
|
|
224
222
|
emulator: CalibrationEmulator = CalibrationEmulator()
|
|
225
223
|
|
|
226
224
|
@classmethod
|
|
227
|
-
def from_bytestr(cls, data: bytes, cape:
|
|
225
|
+
def from_bytestr(cls, data: bytes, cape: CapeData | None = None) -> Self:
|
|
228
226
|
"""Instantiate calibration data based on byte string.
|
|
229
227
|
|
|
230
228
|
This is mainly used to deserialize data read from an EEPROM memory.
|
|
@@ -276,7 +274,7 @@ class CalibrationSeries(ShpModel):
|
|
|
276
274
|
@validate_call(validate_return=False)
|
|
277
275
|
def from_cal(
|
|
278
276
|
cls,
|
|
279
|
-
cal:
|
|
277
|
+
cal: CalibrationHarvester | CalibrationEmulator,
|
|
280
278
|
*,
|
|
281
279
|
emu_port_a: bool = True,
|
|
282
280
|
) -> Self:
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Annotated
|
|
5
|
-
from typing import Optional
|
|
6
5
|
from uuid import uuid4
|
|
7
6
|
|
|
8
7
|
from pydantic import Field
|
|
@@ -37,8 +36,8 @@ class ContentModel(ShpModel):
|
|
|
37
36
|
default_factory=id_default,
|
|
38
37
|
)
|
|
39
38
|
name: NameStr
|
|
40
|
-
description: Annotated[
|
|
41
|
-
comment:
|
|
39
|
+
description: Annotated[SafeStr | None, Field(description="Required when public")] = None
|
|
40
|
+
comment: SafeStr | None = None
|
|
42
41
|
created: datetime = Field(default_factory=datetime.now)
|
|
43
42
|
updated_last: datetime = Field(default_factory=datetime.now)
|
|
44
43
|
|
|
@@ -8,8 +8,6 @@ from datetime import timedelta
|
|
|
8
8
|
from ipaddress import IPv4Address
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
|
-
from typing import Optional
|
|
12
|
-
from typing import Union
|
|
13
11
|
from uuid import UUID
|
|
14
12
|
|
|
15
13
|
import yaml
|
|
@@ -24,7 +22,7 @@ from .wrapper import Wrapper
|
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
def path2str(
|
|
27
|
-
dumper: SafeDumper, data:
|
|
25
|
+
dumper: SafeDumper, data: pathlib.Path | pathlib.WindowsPath | pathlib.PosixPath
|
|
28
26
|
) -> Node:
|
|
29
27
|
"""Add a yaml-representation for a specific datatype."""
|
|
30
28
|
return dumper.represent_scalar("tag:yaml.org,2002:str", str(data.as_posix()))
|
|
@@ -48,6 +46,24 @@ yaml.add_representer(IPv4Address, generic2str, SafeDumper)
|
|
|
48
46
|
yaml.add_representer(UUID, generic2str, SafeDumper)
|
|
49
47
|
|
|
50
48
|
|
|
49
|
+
def path_to_str(old: dict) -> dict:
|
|
50
|
+
r"""Allow platform-independent pickling of ShpModel.
|
|
51
|
+
|
|
52
|
+
Helper Fn
|
|
53
|
+
Posix-Paths (/xyz/abc) in WindowsPath gets converted to \\xyz\\abc when exported
|
|
54
|
+
intended usage: pickle.dump(path_to_str(model.model_dump()))
|
|
55
|
+
"""
|
|
56
|
+
new: dict = {}
|
|
57
|
+
for key, value in old.items():
|
|
58
|
+
if isinstance(value, Path):
|
|
59
|
+
new[key] = value.as_posix().replace("\\", "/")
|
|
60
|
+
elif isinstance(value, dict):
|
|
61
|
+
new[key] = path_to_str(value)
|
|
62
|
+
else:
|
|
63
|
+
new[key] = value
|
|
64
|
+
return new
|
|
65
|
+
|
|
66
|
+
|
|
51
67
|
class ShpModel(BaseModel):
|
|
52
68
|
"""Pre-configured Pydantic Base-Model (specifically for shepherd).
|
|
53
69
|
|
|
@@ -114,7 +130,7 @@ class ShpModel(BaseModel):
|
|
|
114
130
|
yield key, self[key]
|
|
115
131
|
|
|
116
132
|
@classmethod
|
|
117
|
-
def schema_to_file(cls, path:
|
|
133
|
+
def schema_to_file(cls, path: str | Path) -> None:
|
|
118
134
|
"""Store schema to yaml (for frontend-generators)."""
|
|
119
135
|
model_dict = cls.model_json_schema()
|
|
120
136
|
model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
|
|
@@ -123,8 +139,8 @@ class ShpModel(BaseModel):
|
|
|
123
139
|
|
|
124
140
|
def to_file(
|
|
125
141
|
self,
|
|
126
|
-
path:
|
|
127
|
-
comment:
|
|
142
|
+
path: str | Path,
|
|
143
|
+
comment: str | None = None,
|
|
128
144
|
*,
|
|
129
145
|
minimal: bool = True,
|
|
130
146
|
use_pickle: bool = False,
|
|
@@ -135,21 +151,21 @@ class ShpModel(BaseModel):
|
|
|
135
151
|
pickle: uses pickle to serialize data, on BBB >100x faster for large files
|
|
136
152
|
comment: documentation.
|
|
137
153
|
"""
|
|
138
|
-
model_dict = self.model_dump(exclude_unset=minimal)
|
|
139
154
|
model_wrap = Wrapper(
|
|
140
155
|
datatype=type(self).__name__,
|
|
141
156
|
comment=comment,
|
|
142
157
|
created=local_now(),
|
|
143
|
-
parameters=
|
|
158
|
+
parameters=self.model_dump(exclude_unset=minimal),
|
|
144
159
|
)
|
|
160
|
+
model_dict = model_wrap.model_dump(exclude_unset=minimal, exclude_defaults=minimal)
|
|
145
161
|
if use_pickle:
|
|
146
|
-
model_serial = pickle.dumps(model_dict
|
|
162
|
+
model_serial = pickle.dumps(path_to_str(model_dict))
|
|
147
163
|
model_path = Path(path).resolve().with_suffix(".pickle")
|
|
148
164
|
else:
|
|
149
165
|
# TODO: x64 windows supports CSafeLoader/dumper,
|
|
150
166
|
# there are examples that replace load if avail
|
|
151
167
|
model_serial = yaml.safe_dump(
|
|
152
|
-
|
|
168
|
+
model_dict,
|
|
153
169
|
default_flow_style=False,
|
|
154
170
|
sort_keys=False,
|
|
155
171
|
)
|
|
@@ -158,25 +174,25 @@ class ShpModel(BaseModel):
|
|
|
158
174
|
|
|
159
175
|
if not model_path.parent.exists():
|
|
160
176
|
model_path.parent.mkdir(parents=True)
|
|
161
|
-
with model_path.open("w") as f:
|
|
177
|
+
with model_path.open("wb" if use_pickle else "w") as f:
|
|
162
178
|
f.write(model_serial)
|
|
163
179
|
return model_path
|
|
164
180
|
|
|
165
181
|
@classmethod
|
|
166
|
-
def from_file(cls, path:
|
|
182
|
+
def from_file(cls, path: str | Path) -> Self:
|
|
167
183
|
"""Load from YAML or pickle file."""
|
|
168
184
|
path: Path = Path(path)
|
|
169
185
|
if not Path(path).exists():
|
|
170
186
|
raise FileNotFoundError
|
|
171
187
|
if path.suffix.lower() == ".pickle":
|
|
172
188
|
with Path(path).open("rb") as shp_file:
|
|
173
|
-
|
|
189
|
+
shp_dict = pickle.load(shp_file) # noqa: S301
|
|
174
190
|
else:
|
|
175
191
|
with Path(path).open() as shp_file:
|
|
176
192
|
shp_dict = yaml.safe_load(shp_file)
|
|
177
|
-
|
|
193
|
+
shp_wrap = Wrapper(**shp_dict)
|
|
178
194
|
if shp_wrap.datatype != cls.__name__:
|
|
179
|
-
raise ValueError("Model in file does not match the
|
|
195
|
+
raise ValueError("Model in file does not match the actual Class")
|
|
180
196
|
return cls(**shp_wrap.parameters)
|
|
181
197
|
|
|
182
198
|
def get_hash(self) -> str:
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from typing import Annotated
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
from pydantic import BaseModel
|
|
8
7
|
from pydantic import StringConstraints
|
|
@@ -18,10 +17,10 @@ class Wrapper(BaseModel):
|
|
|
18
17
|
|
|
19
18
|
datatype: str
|
|
20
19
|
""" ⤷ model-name"""
|
|
21
|
-
comment:
|
|
22
|
-
created:
|
|
20
|
+
comment: SafeStrClone | None = None
|
|
21
|
+
created: datetime | None = None
|
|
23
22
|
""" ⤷ Optional metadata"""
|
|
24
|
-
lib_ver:
|
|
23
|
+
lib_ver: str | None = version
|
|
25
24
|
""" ⤷ for debug-purposes and later compatibility-checks"""
|
|
26
25
|
parameters: dict
|
|
27
26
|
""" ⤷ ShpModel"""
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Any
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
from pydantic import PositiveFloat
|
|
9
8
|
from pydantic import model_validator
|
|
@@ -40,10 +39,10 @@ class EnergyEnvironment(ContentModel):
|
|
|
40
39
|
# TODO: harvester, transducer
|
|
41
40
|
|
|
42
41
|
# additional descriptive metadata, TODO: these are very solar-centered -> generalize
|
|
43
|
-
light_source:
|
|
44
|
-
weather_conditions:
|
|
45
|
-
indoor:
|
|
46
|
-
location:
|
|
42
|
+
light_source: str | None = None
|
|
43
|
+
weather_conditions: str | None = None
|
|
44
|
+
indoor: bool | None = None
|
|
45
|
+
location: str | None = None
|
|
47
46
|
|
|
48
47
|
@model_validator(mode="before")
|
|
49
48
|
@classmethod
|
|
@@ -6,9 +6,7 @@ TODO: should be more generalized - currently only supports msp & nRF
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Annotated
|
|
8
8
|
from typing import Any
|
|
9
|
-
from typing import Optional
|
|
10
9
|
from typing import TypedDict
|
|
11
|
-
from typing import Union
|
|
12
10
|
|
|
13
11
|
from pydantic import StringConstraints
|
|
14
12
|
from pydantic import model_validator
|
|
@@ -58,9 +56,9 @@ class Firmware(ContentModel, title="Firmware of Target"):
|
|
|
58
56
|
|
|
59
57
|
mcu: MCU
|
|
60
58
|
|
|
61
|
-
data:
|
|
59
|
+
data: FirmwareStr | Path
|
|
62
60
|
data_type: FirmwareDType
|
|
63
|
-
data_hash:
|
|
61
|
+
data_hash: str | None = None
|
|
64
62
|
data_local: bool = True
|
|
65
63
|
""" ⤷ signals that file has to be copied to testbed"""
|
|
66
64
|
|
|
@@ -147,7 +145,7 @@ class Firmware(ContentModel, title="Firmware of Target"):
|
|
|
147
145
|
kwargs["name"] = file.name
|
|
148
146
|
return cls(**kwargs)
|
|
149
147
|
|
|
150
|
-
def compare_hash(self, path:
|
|
148
|
+
def compare_hash(self, path: Path | None = None) -> bool:
|
|
151
149
|
if self.data_hash is None:
|
|
152
150
|
return True
|
|
153
151
|
|
|
@@ -4,7 +4,6 @@ from collections.abc import Mapping
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
from typing import Any
|
|
7
|
-
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from pydantic import Field
|
|
10
9
|
from pydantic import model_validator
|
|
@@ -214,7 +213,7 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
214
213
|
(constant current tracking).
|
|
215
214
|
"""
|
|
216
215
|
|
|
217
|
-
voltage_step_mV:
|
|
216
|
+
voltage_step_mV: Annotated[float, Field(ge=1, le=1000000)] | None = None
|
|
218
217
|
"""The difference between two adjacent voltage samples.
|
|
219
218
|
|
|
220
219
|
This value is implicitly derived from the other ramp parameters:
|
|
@@ -384,7 +383,7 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
384
383
|
|
|
385
384
|
def calc_window_size(
|
|
386
385
|
self,
|
|
387
|
-
dtype_in:
|
|
386
|
+
dtype_in: EnergyDType | None = None,
|
|
388
387
|
*,
|
|
389
388
|
for_emu: bool,
|
|
390
389
|
) -> int:
|
|
@@ -468,9 +467,9 @@ class HarvesterPRUConfig(ShpModel):
|
|
|
468
467
|
def from_vhrv(
|
|
469
468
|
cls,
|
|
470
469
|
data: VirtualHarvesterConfig,
|
|
471
|
-
dtype_in:
|
|
472
|
-
window_size:
|
|
473
|
-
voltage_step_V:
|
|
470
|
+
dtype_in: EnergyDType | None = EnergyDType.ivsample,
|
|
471
|
+
window_size: u32 | None = None,
|
|
472
|
+
voltage_step_V: float | None = None,
|
|
474
473
|
*,
|
|
475
474
|
for_emu: bool = False,
|
|
476
475
|
) -> Self:
|
|
@@ -4,15 +4,12 @@ from collections.abc import Iterable
|
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
from datetime import timedelta
|
|
6
6
|
from typing import Annotated
|
|
7
|
-
from typing import Optional
|
|
8
7
|
|
|
9
8
|
from pydantic import Field
|
|
10
9
|
from pydantic import model_validator
|
|
11
10
|
from typing_extensions import Self
|
|
12
|
-
from typing_extensions import deprecated
|
|
13
11
|
|
|
14
12
|
from shepherd_core.config import config
|
|
15
|
-
from shepherd_core.data_models.base.content import IdInt
|
|
16
13
|
from shepherd_core.data_models.base.content import NameStr
|
|
17
14
|
from shepherd_core.data_models.base.content import SafeStr
|
|
18
15
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
@@ -33,10 +30,10 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
33
30
|
|
|
34
31
|
# General Properties
|
|
35
32
|
name: NameStr
|
|
36
|
-
description: Annotated[
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
comment:
|
|
33
|
+
description: Annotated[SafeStr | None, Field(description="Required for public instances")] = (
|
|
34
|
+
None
|
|
35
|
+
)
|
|
36
|
+
comment: SafeStr | None = None
|
|
40
37
|
|
|
41
38
|
# feedback
|
|
42
39
|
email_results: bool = True
|
|
@@ -44,20 +41,14 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
44
41
|
sys_logging: SystemLogging = sys_log_all
|
|
45
42
|
|
|
46
43
|
# schedule
|
|
47
|
-
time_start:
|
|
48
|
-
duration:
|
|
44
|
+
time_start: datetime | None = None # = ASAP
|
|
45
|
+
duration: timedelta | None = None # = till EOF
|
|
49
46
|
|
|
50
47
|
# targets
|
|
51
48
|
target_configs: Annotated[list[TargetConfig], Field(min_length=1, max_length=128)]
|
|
52
49
|
|
|
53
50
|
# debug
|
|
54
|
-
lib_ver:
|
|
55
|
-
|
|
56
|
-
# deprecated fields, TODO: remove before public release
|
|
57
|
-
id: Annotated[Optional[int], deprecated("not needed")] = None
|
|
58
|
-
created: Annotated[Optional[datetime], deprecated("not needed")] = None
|
|
59
|
-
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
60
|
-
owner_id: Annotated[Optional[IdInt], deprecated("not needed")] = None
|
|
51
|
+
lib_ver: str | None = version
|
|
61
52
|
|
|
62
53
|
@model_validator(mode="after")
|
|
63
54
|
def post_validation(self) -> Self:
|
|
@@ -109,7 +100,8 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
109
100
|
msg = f"Target-ID {target_id} was not found in Experiment '{self.name}'"
|
|
110
101
|
raise ValueError(msg)
|
|
111
102
|
|
|
112
|
-
def folder_name(self, custom_date:
|
|
103
|
+
def folder_name(self, custom_date: datetime | None = None) -> str:
|
|
104
|
+
# TODO: custom date should not overrule time_start
|
|
113
105
|
date = custom_date if custom_date is not None else self.time_start
|
|
114
106
|
timestamp = local_now() if date is None else date
|
|
115
107
|
timestrng = timestamp.strftime("%Y-%m-%d_%H-%M-%S")
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from datetime import timedelta
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from typing import Annotated
|
|
6
|
-
from typing import Optional
|
|
7
6
|
|
|
8
7
|
import numpy as np
|
|
9
8
|
from annotated_types import Interval
|
|
@@ -11,7 +10,6 @@ from pydantic import Field
|
|
|
11
10
|
from pydantic import PositiveFloat
|
|
12
11
|
from pydantic import model_validator
|
|
13
12
|
from typing_extensions import Self
|
|
14
|
-
from typing_extensions import deprecated
|
|
15
13
|
|
|
16
14
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
17
15
|
from shepherd_core.data_models.testbed.gpio import GPIO
|
|
@@ -22,10 +20,7 @@ zero_duration = timedelta(seconds=0)
|
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
25
|
-
"""Configuration for recording the Power-Consumption of the Target Nodes.
|
|
26
|
-
|
|
27
|
-
TODO: postprocessing not implemented ATM
|
|
28
|
-
"""
|
|
23
|
+
"""Configuration for recording the Power-Consumption of the Target Nodes."""
|
|
29
24
|
|
|
30
25
|
intermediate_voltage: bool = False
|
|
31
26
|
"""
|
|
@@ -35,20 +30,20 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
35
30
|
# time
|
|
36
31
|
delay: timedelta = zero_duration
|
|
37
32
|
"""start recording after experiment started"""
|
|
38
|
-
duration:
|
|
33
|
+
duration: timedelta | None = None # till EOF
|
|
39
34
|
"""duration of recording after delay starts the process.
|
|
40
35
|
|
|
41
36
|
default is None, recording till EOF"""
|
|
42
37
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
""" ⤷ reduce file-size by calculating power
|
|
38
|
+
# further processing of IV-Samples
|
|
39
|
+
only_power: bool = False
|
|
40
|
+
""" ⤷ reduce file-size by calculating power and automatically discard I&V"""
|
|
46
41
|
samplerate: Annotated[int, Field(ge=10, le=100_000)] = 100_000
|
|
47
|
-
""" ⤷ reduce file-size by
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"""
|
|
42
|
+
""" ⤷ reduce file-size by re-sampling (mean over x samples)
|
|
43
|
+
Timestamps will be taken from the start of that sample-window.
|
|
44
|
+
example: 10 Hz samplerate will be binning 10k samples via mean() and
|
|
45
|
+
the timestamp for that new sample will be value[0] of the 10k long vector
|
|
46
|
+
"""
|
|
52
47
|
|
|
53
48
|
@model_validator(mode="after")
|
|
54
49
|
def post_validation(self) -> Self:
|
|
@@ -57,22 +52,10 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
57
52
|
if self.duration and self.duration.total_seconds() < 0:
|
|
58
53
|
raise ValueError("Duration can't be negative.")
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
if
|
|
62
|
-
raise ValueError(
|
|
63
|
-
|
|
64
|
-
raise NotImplementedError(
|
|
65
|
-
"Feature PowerTracing.calculate_power reserved for future use."
|
|
66
|
-
)
|
|
67
|
-
if self.samplerate != 100_000:
|
|
68
|
-
raise NotImplementedError("Feature PowerTracing.samplerate reserved for future use.")
|
|
69
|
-
if self.discard_current:
|
|
70
|
-
raise NotImplementedError(
|
|
71
|
-
"Feature PowerTracing.discard_current reserved for future use."
|
|
72
|
-
)
|
|
73
|
-
if self.discard_voltage:
|
|
74
|
-
raise NotImplementedError(
|
|
75
|
-
"Feature PowerTracing.discard_voltage reserved for future use."
|
|
55
|
+
rates_allowed = (10, 100, 1_000, 100_000)
|
|
56
|
+
if self.samplerate not in rates_allowed:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"Feature PowerTracing.samplerate only supports specific rates: %s", rates_allowed
|
|
76
59
|
)
|
|
77
60
|
return self
|
|
78
61
|
|
|
@@ -175,7 +158,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
175
158
|
|
|
176
159
|
# time
|
|
177
160
|
delay: timedelta = zero_duration
|
|
178
|
-
duration:
|
|
161
|
+
duration: timedelta | None = None # till EOF
|
|
179
162
|
|
|
180
163
|
# post-processing,
|
|
181
164
|
uart_decode: bool = False
|
|
@@ -264,11 +247,6 @@ class SystemLogging(ShpModel, title="Config for System-Logging"):
|
|
|
264
247
|
sheep: bool = True
|
|
265
248
|
sys_util: bool = True
|
|
266
249
|
|
|
267
|
-
# deprecated, TODO: remove lines below before public release
|
|
268
|
-
dmesg: Annotated[bool, deprecated("for sheep v0.9.0+, use 'kernel' instead")] = True
|
|
269
|
-
ptp: Annotated[bool, deprecated("for sheep v0.9.0+, use 'time_sync' instead")] = True
|
|
270
|
-
shepherd: Annotated[bool, deprecated("for sheep v0.9.0+, use 'sheep' instead")] = True
|
|
271
|
-
|
|
272
250
|
|
|
273
251
|
# TODO: some more interaction would be good
|
|
274
252
|
# - execute limited python-scripts
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Configuration related to Target Nodes (DuT)."""
|
|
2
2
|
|
|
3
3
|
from typing import Annotated
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
from pydantic import Field
|
|
7
6
|
from pydantic import model_validator
|
|
@@ -28,7 +27,7 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
28
27
|
"""Configuration related to Target Nodes (DuT)."""
|
|
29
28
|
|
|
30
29
|
target_IDs: Annotated[list[IdInt], Field(min_length=1, max_length=128)]
|
|
31
|
-
custom_IDs:
|
|
30
|
+
custom_IDs: Annotated[list[IdInt16], Field(min_length=1, max_length=128)] | None = None
|
|
32
31
|
""" ⤷ custom ID will replace 'const uint16_t SHEPHERD_NODE_ID' in firmware.
|
|
33
32
|
|
|
34
33
|
if no custom ID is provided, the original ID of target is used
|
|
@@ -37,9 +36,9 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
37
36
|
energy_env: EnergyEnvironment
|
|
38
37
|
""" input for the virtual source """
|
|
39
38
|
virtual_source: VirtualSourceConfig = vsrc_neutral
|
|
40
|
-
target_delays:
|
|
41
|
-
Annotated[list[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)]
|
|
42
|
-
|
|
39
|
+
target_delays: (
|
|
40
|
+
Annotated[list[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)] | None
|
|
41
|
+
) = None
|
|
43
42
|
""" ⤷ individual starting times
|
|
44
43
|
|
|
45
44
|
- allows to use the same environment
|
|
@@ -48,13 +47,13 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
48
47
|
|
|
49
48
|
firmware1: Firmware
|
|
50
49
|
""" ⤷ omitted FW gets set to neutral deep-sleep"""
|
|
51
|
-
firmware2:
|
|
50
|
+
firmware2: Firmware | None = None
|
|
52
51
|
""" ⤷ omitted FW gets set to neutral deep-sleep"""
|
|
53
52
|
|
|
54
|
-
power_tracing:
|
|
55
|
-
gpio_tracing:
|
|
56
|
-
gpio_actuation:
|
|
57
|
-
uart_logging:
|
|
53
|
+
power_tracing: PowerTracing | None = None
|
|
54
|
+
gpio_tracing: GpioTracing | None = None
|
|
55
|
+
gpio_actuation: GpioActuation | None = None
|
|
56
|
+
uart_logging: UartLogging | None = None
|
|
58
57
|
|
|
59
58
|
@model_validator(mode="after")
|
|
60
59
|
def post_validation(self) -> Self:
|
|
@@ -96,7 +95,7 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
96
95
|
raise NotImplementedError("Feature GpioActuation reserved for future use.")
|
|
97
96
|
return self
|
|
98
97
|
|
|
99
|
-
def get_custom_id(self, target_id: int) ->
|
|
98
|
+
def get_custom_id(self, target_id: int) -> int | None:
|
|
100
99
|
if self.custom_IDs is not None and target_id in self.target_IDs:
|
|
101
100
|
return self.custom_IDs[self.target_IDs.index(target_id)]
|
|
102
101
|
return None
|
|
@@ -5,8 +5,6 @@ These models import externally from all other model-modules!
|
|
|
5
5
|
|
|
6
6
|
import pickle
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Optional
|
|
9
|
-
from typing import Union
|
|
10
8
|
|
|
11
9
|
import yaml
|
|
12
10
|
|
|
@@ -35,7 +33,7 @@ __all__ = [
|
|
|
35
33
|
]
|
|
36
34
|
|
|
37
35
|
|
|
38
|
-
def prepare_task(config:
|
|
36
|
+
def prepare_task(config: ShpModel | Path | str, observer: str | None = None) -> Wrapper:
|
|
39
37
|
"""Open file and extract tasks.
|
|
40
38
|
|
|
41
39
|
- Open file (from Path or str of Path)
|
|
@@ -47,7 +45,8 @@ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = N
|
|
|
47
45
|
|
|
48
46
|
if isinstance(config, Path) and config.exists() and config.suffix.lower() == ".pickle":
|
|
49
47
|
with config.resolve().open("rb") as shp_file:
|
|
50
|
-
|
|
48
|
+
shp_dict = pickle.load(shp_file) # noqa: S301
|
|
49
|
+
shp_wrap = Wrapper(**shp_dict)
|
|
51
50
|
elif isinstance(config, Path) and config.exists() and config.suffix.lower() == ".yaml":
|
|
52
51
|
with config.resolve().open() as shp_file:
|
|
53
52
|
shp_dict = yaml.safe_load(shp_file)
|
|
@@ -8,14 +8,11 @@ from enum import Enum
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from pathlib import PurePosixPath
|
|
10
10
|
from typing import Annotated
|
|
11
|
-
from typing import Optional
|
|
12
|
-
from typing import Union
|
|
13
11
|
|
|
14
12
|
from pydantic import Field
|
|
15
13
|
from pydantic import model_validator
|
|
16
14
|
from pydantic import validate_call
|
|
17
15
|
from typing_extensions import Self
|
|
18
|
-
from typing_extensions import deprecated
|
|
19
16
|
|
|
20
17
|
from shepherd_core.data_models.base.content import IdInt
|
|
21
18
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
@@ -55,7 +52,7 @@ class EmulationTask(ShpModel):
|
|
|
55
52
|
# General config
|
|
56
53
|
input_path: Path
|
|
57
54
|
""" ⤷ hdf5 file containing harvesting data"""
|
|
58
|
-
output_path:
|
|
55
|
+
output_path: Path | None = None
|
|
59
56
|
""" ⤷ dir- or file-path for storing the recorded data:
|
|
60
57
|
|
|
61
58
|
- providing a directory -> file is named emu_timestamp.h5
|
|
@@ -65,13 +62,12 @@ class EmulationTask(ShpModel):
|
|
|
65
62
|
"""
|
|
66
63
|
force_overwrite: bool = False
|
|
67
64
|
""" ⤷ Overwrite existing file"""
|
|
68
|
-
output_compression:
|
|
65
|
+
output_compression: Compression | None = Compression.default
|
|
69
66
|
""" ⤷ should be lzf, 1 (gzip level 1) or None (order of recommendation)"""
|
|
70
|
-
time_start:
|
|
67
|
+
time_start: datetime | None = None
|
|
71
68
|
""" timestamp or unix epoch time, None = ASAP"""
|
|
72
|
-
duration:
|
|
69
|
+
duration: timedelta | None = None
|
|
73
70
|
""" ⤷ Duration of recording in seconds, None = till EOF"""
|
|
74
|
-
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
75
71
|
|
|
76
72
|
# emulation-specific
|
|
77
73
|
use_cal_default: bool = False
|
|
@@ -91,7 +87,7 @@ class EmulationTask(ShpModel):
|
|
|
91
87
|
- main channel is nnected to virtual Source
|
|
92
88
|
- the other port is aux
|
|
93
89
|
"""
|
|
94
|
-
voltage_aux:
|
|
90
|
+
voltage_aux: Annotated[float, Field(ge=0, le=4.5)] | str = 0
|
|
95
91
|
""" ⤷ aux_voltage options
|
|
96
92
|
- 0-4.5 for specific const Voltage (0 V = disabled),
|
|
97
93
|
- "buffer" will output intermediate voltage (storage cap of vsource),
|
|
@@ -104,11 +100,11 @@ class EmulationTask(ShpModel):
|
|
|
104
100
|
provide parameters or name like BQ25570
|
|
105
101
|
"""
|
|
106
102
|
|
|
107
|
-
power_tracing:
|
|
108
|
-
gpio_tracing:
|
|
109
|
-
uart_logging:
|
|
110
|
-
gpio_actuation:
|
|
111
|
-
sys_logging:
|
|
103
|
+
power_tracing: PowerTracing | None = PowerTracing()
|
|
104
|
+
gpio_tracing: GpioTracing | None = GpioTracing()
|
|
105
|
+
uart_logging: UartLogging | None = UartLogging()
|
|
106
|
+
gpio_actuation: GpioActuation | None = None
|
|
107
|
+
sys_logging: SystemLogging | None = SystemLogging()
|
|
112
108
|
|
|
113
109
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
114
110
|
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug,
|