shepherd-core 2025.5.3__py3-none-any.whl → 2025.6.2__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 +2 -2
- shepherd_core/commons.py +3 -5
- shepherd_core/config.py +34 -0
- shepherd_core/data_models/__init__.py +1 -1
- shepherd_core/data_models/base/calibration.py +13 -8
- shepherd_core/data_models/base/shepherd.py +28 -11
- shepherd_core/data_models/base/wrapper.py +4 -4
- shepherd_core/data_models/content/energy_environment.py +1 -1
- shepherd_core/data_models/content/firmware.py +13 -8
- shepherd_core/data_models/content/virtual_harvester.py +13 -13
- shepherd_core/data_models/content/virtual_source.py +41 -33
- shepherd_core/data_models/content/virtual_source_fixture.yaml +1 -1
- shepherd_core/data_models/experiment/experiment.py +28 -18
- shepherd_core/data_models/experiment/observer_features.py +32 -13
- shepherd_core/data_models/experiment/target_config.py +17 -7
- shepherd_core/data_models/task/__init__.py +8 -4
- shepherd_core/data_models/task/emulation.py +52 -30
- shepherd_core/data_models/task/firmware_mod.py +15 -6
- shepherd_core/data_models/task/harvest.py +19 -13
- shepherd_core/data_models/task/helper_paths.py +15 -0
- shepherd_core/data_models/task/observer_tasks.py +20 -18
- shepherd_core/data_models/task/programming.py +10 -4
- shepherd_core/data_models/task/testbed_tasks.py +16 -7
- shepherd_core/data_models/testbed/cape_fixture.yaml +1 -1
- shepherd_core/data_models/testbed/observer.py +1 -1
- shepherd_core/data_models/testbed/observer_fixture.yaml +2 -2
- shepherd_core/data_models/testbed/target.py +1 -1
- shepherd_core/data_models/testbed/target_fixture.old1 +1 -1
- shepherd_core/data_models/testbed/target_fixture.yaml +1 -1
- shepherd_core/data_models/testbed/testbed.py +8 -9
- shepherd_core/decoder_waveform/uart.py +7 -7
- shepherd_core/fw_tools/patcher.py +13 -14
- shepherd_core/fw_tools/validation.py +2 -2
- shepherd_core/inventory/system.py +3 -5
- shepherd_core/logger.py +3 -3
- shepherd_core/reader.py +9 -2
- shepherd_core/testbed_client/cache_path.py +1 -1
- shepherd_core/testbed_client/client_web.py +2 -2
- shepherd_core/testbed_client/fixtures.py +5 -5
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/virtual_harvester_model.py +2 -2
- shepherd_core/vsource/virtual_source_simulation.py +2 -2
- shepherd_core/writer.py +2 -2
- {shepherd_core-2025.5.3.dist-info → shepherd_core-2025.6.2.dist-info}/METADATA +12 -12
- shepherd_core-2025.6.2.dist-info/RECORD +83 -0
- {shepherd_core-2025.5.3.dist-info → shepherd_core-2025.6.2.dist-info}/WHEEL +1 -1
- shepherd_core-2025.5.3.dist-info/RECORD +0 -81
- {shepherd_core-2025.5.3.dist-info → shepherd_core-2025.6.2.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.5.3.dist-info → shepherd_core-2025.6.2.dist-info}/zip-safe +0 -0
|
@@ -13,9 +13,12 @@ from pydantic import model_validator
|
|
|
13
13
|
from typing_extensions import Self
|
|
14
14
|
from typing_extensions import deprecated
|
|
15
15
|
|
|
16
|
-
from shepherd_core import logger
|
|
17
16
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
18
17
|
from shepherd_core.data_models.testbed.gpio import GPIO
|
|
18
|
+
from shepherd_core.logger import log
|
|
19
|
+
|
|
20
|
+
# defaults (pre-init complex types)
|
|
21
|
+
zero_duration = timedelta(seconds=0)
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
@@ -25,19 +28,27 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
25
28
|
"""
|
|
26
29
|
|
|
27
30
|
intermediate_voltage: bool = False
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
"""
|
|
32
|
+
⤷ for EMU: record storage capacitor instead of output (good for V_out = const)
|
|
33
|
+
this also includes current!
|
|
34
|
+
"""
|
|
31
35
|
# time
|
|
32
|
-
delay: timedelta =
|
|
36
|
+
delay: timedelta = zero_duration
|
|
37
|
+
"""start recording after experiment started"""
|
|
33
38
|
duration: Optional[timedelta] = None # till EOF
|
|
39
|
+
"""duration of recording after delay starts the process.
|
|
40
|
+
|
|
41
|
+
default is None, recording till EOF"""
|
|
34
42
|
|
|
35
43
|
# post-processing
|
|
36
44
|
calculate_power: bool = False
|
|
37
|
-
|
|
45
|
+
""" ⤷ reduce file-size by calculating power -> not implemented ATM"""
|
|
46
|
+
samplerate: Annotated[int, Field(ge=10, le=100_000)] = 100_000
|
|
47
|
+
""" ⤷ reduce file-size by down-sampling -> not implemented ATM"""
|
|
38
48
|
discard_current: bool = False
|
|
49
|
+
""" ⤷ reduce file-size by omitting current -> not implemented ATM"""
|
|
39
50
|
discard_voltage: bool = False
|
|
40
|
-
|
|
51
|
+
""" ⤷ reduce file-size by omitting voltage -> not implemented ATM"""
|
|
41
52
|
|
|
42
53
|
@model_validator(mode="after")
|
|
43
54
|
def post_validation(self) -> Self:
|
|
@@ -163,11 +174,12 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
163
174
|
"""
|
|
164
175
|
|
|
165
176
|
# time
|
|
166
|
-
delay: timedelta =
|
|
177
|
+
delay: timedelta = zero_duration
|
|
167
178
|
duration: Optional[timedelta] = None # till EOF
|
|
168
179
|
|
|
169
180
|
# post-processing,
|
|
170
181
|
uart_decode: bool = False
|
|
182
|
+
"""Automatic decoding from gpio-trace not implemented ATM."""
|
|
171
183
|
uart_pin: GPIO = GPIO(name="GPIO8")
|
|
172
184
|
uart_baudrate: Annotated[int, Field(ge=2_400, le=1_152_000)] = 115_200
|
|
173
185
|
|
|
@@ -178,7 +190,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
|
178
190
|
if self.duration and self.duration.total_seconds() < 0:
|
|
179
191
|
raise ValueError("Duration can't be negative.")
|
|
180
192
|
if self.uart_decode:
|
|
181
|
-
|
|
193
|
+
log.error(
|
|
182
194
|
"Feature GpioTracing.uart_decode reserved for future use. "
|
|
183
195
|
"Use UartLogging or manually decode serial with the provided waveform decoder."
|
|
184
196
|
)
|
|
@@ -205,12 +217,14 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
|
|
|
205
217
|
"""Configuration for a single GPIO-Event (Actuation)."""
|
|
206
218
|
|
|
207
219
|
delay: PositiveFloat
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
""" ⤷ from start_time
|
|
221
|
+
|
|
222
|
+
- resolution 10 us (guaranteed, but finer steps are possible)
|
|
223
|
+
"""
|
|
210
224
|
gpio: GPIO
|
|
211
225
|
level: GpioLevel
|
|
212
226
|
period: Annotated[float, Field(ge=10e-6)] = 1
|
|
213
|
-
|
|
227
|
+
""" ⤷ time base of periodicity in s"""
|
|
214
228
|
count: Annotated[int, Field(ge=1, le=4096)] = 1
|
|
215
229
|
|
|
216
230
|
@model_validator(mode="after")
|
|
@@ -233,6 +247,11 @@ class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
|
|
|
233
247
|
|
|
234
248
|
events: Annotated[list[GpioEvent], Field(min_length=1, max_length=1024)]
|
|
235
249
|
|
|
250
|
+
@model_validator(mode="after")
|
|
251
|
+
def post_validation(self) -> Self:
|
|
252
|
+
msg = "not implemented ATM"
|
|
253
|
+
raise ValueError(msg)
|
|
254
|
+
|
|
236
255
|
def get_gpios(self) -> set:
|
|
237
256
|
return {_ev.gpio for _ev in self.events}
|
|
238
257
|
|
|
@@ -245,7 +264,7 @@ class SystemLogging(ShpModel, title="Config for System-Logging"):
|
|
|
245
264
|
sheep: bool = True
|
|
246
265
|
sys_util: bool = True
|
|
247
266
|
|
|
248
|
-
# TODO: remove lines below
|
|
267
|
+
# deprecated, TODO: remove lines below before public release
|
|
249
268
|
dmesg: Annotated[bool, deprecated("for sheep v0.9.0+, use 'kernel' instead")] = True
|
|
250
269
|
ptp: Annotated[bool, deprecated("for sheep v0.9.0+, use 'time_sync' instead")] = True
|
|
251
270
|
shepherd: Annotated[bool, deprecated("for sheep v0.9.0+, use 'sheep' instead")] = True
|
|
@@ -20,26 +20,36 @@ from .observer_features import GpioTracing
|
|
|
20
20
|
from .observer_features import PowerTracing
|
|
21
21
|
from .observer_features import UartLogging
|
|
22
22
|
|
|
23
|
+
# defaults (pre-init complex types)
|
|
24
|
+
vsrc_neutral = VirtualSourceConfig(name="neutral")
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
class TargetConfig(ShpModel, title="Target Config"):
|
|
25
28
|
"""Configuration related to Target Nodes (DuT)."""
|
|
26
29
|
|
|
27
30
|
target_IDs: Annotated[list[IdInt], Field(min_length=1, max_length=128)]
|
|
28
31
|
custom_IDs: Optional[Annotated[list[IdInt16], Field(min_length=1, max_length=128)]] = None
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
""" ⤷ custom ID will replace 'const uint16_t SHEPHERD_NODE_ID' in firmware.
|
|
33
|
+
|
|
34
|
+
if no custom ID is provided, the original ID of target is used
|
|
35
|
+
"""
|
|
31
36
|
|
|
32
|
-
energy_env: EnergyEnvironment
|
|
33
|
-
|
|
37
|
+
energy_env: EnergyEnvironment
|
|
38
|
+
""" input for the virtual source """
|
|
39
|
+
virtual_source: VirtualSourceConfig = vsrc_neutral
|
|
34
40
|
target_delays: Optional[
|
|
35
41
|
Annotated[list[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)]
|
|
36
42
|
] = None
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
""" ⤷ individual starting times
|
|
44
|
+
|
|
45
|
+
- allows to use the same environment
|
|
46
|
+
- not implemented ATM
|
|
47
|
+
"""
|
|
39
48
|
|
|
40
49
|
firmware1: Firmware
|
|
50
|
+
""" ⤷ omitted FW gets set to neutral deep-sleep"""
|
|
41
51
|
firmware2: Optional[Firmware] = None
|
|
42
|
-
|
|
52
|
+
""" ⤷ omitted FW gets set to neutral deep-sleep"""
|
|
43
53
|
|
|
44
54
|
power_tracing: Optional[PowerTracing] = None
|
|
45
55
|
gpio_tracing: Optional[GpioTracing] = None
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
These models import externally from all other model-modules!
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
import pickle
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Optional
|
|
8
9
|
from typing import Union
|
|
@@ -11,7 +12,7 @@ import yaml
|
|
|
11
12
|
|
|
12
13
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
13
14
|
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
14
|
-
from shepherd_core.logger import
|
|
15
|
+
from shepherd_core.logger import log
|
|
15
16
|
|
|
16
17
|
from .emulation import Compression
|
|
17
18
|
from .emulation import EmulationTask
|
|
@@ -44,7 +45,10 @@ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = N
|
|
|
44
45
|
if isinstance(config, str):
|
|
45
46
|
config = Path(config)
|
|
46
47
|
|
|
47
|
-
if isinstance(config, Path):
|
|
48
|
+
if isinstance(config, Path) and config.exists() and config.suffix.lower() == ".pickle":
|
|
49
|
+
with config.resolve().open("rb") as shp_file:
|
|
50
|
+
shp_wrap = pickle.load(shp_file, fix_imports=True) # noqa: S301
|
|
51
|
+
elif isinstance(config, Path) and config.exists() and config.suffix.lower() == ".yaml":
|
|
48
52
|
with config.resolve().open() as shp_file:
|
|
49
53
|
shp_dict = yaml.safe_load(shp_file)
|
|
50
54
|
shp_wrap = Wrapper(**shp_dict)
|
|
@@ -59,12 +63,12 @@ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = N
|
|
|
59
63
|
|
|
60
64
|
if shp_wrap.datatype == TestbedTasks.__name__:
|
|
61
65
|
if observer is None:
|
|
62
|
-
|
|
66
|
+
log.debug(
|
|
63
67
|
"Task-Set contained TestbedTasks & no observer was provided -> will return TB-Tasks"
|
|
64
68
|
)
|
|
65
69
|
return shp_wrap
|
|
66
70
|
tbt = TestbedTasks(**shp_wrap.parameters)
|
|
67
|
-
|
|
71
|
+
log.debug("Loading Testbed-Tasks %s for %s", tbt.name, observer)
|
|
68
72
|
obt = tbt.get_observer_tasks(observer)
|
|
69
73
|
if obt is None:
|
|
70
74
|
msg = f"Observer '{observer}' is not in TestbedTask-Set"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Configuration for the Observer in Emulation-Mode."""
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
+
from collections.abc import Set as AbstractSet
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from datetime import timedelta
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from pathlib import PurePosixPath
|
|
8
10
|
from typing import Annotated
|
|
9
11
|
from typing import Optional
|
|
10
12
|
from typing import Union
|
|
@@ -25,9 +27,12 @@ from shepherd_core.data_models.experiment.observer_features import GpioTracing
|
|
|
25
27
|
from shepherd_core.data_models.experiment.observer_features import PowerTracing
|
|
26
28
|
from shepherd_core.data_models.experiment.observer_features import SystemLogging
|
|
27
29
|
from shepherd_core.data_models.experiment.observer_features import UartLogging
|
|
30
|
+
from shepherd_core.data_models.experiment.target_config import vsrc_neutral
|
|
28
31
|
from shepherd_core.data_models.testbed import Testbed
|
|
29
32
|
from shepherd_core.data_models.testbed.cape import TargetPort
|
|
30
|
-
from shepherd_core.logger import
|
|
33
|
+
from shepherd_core.logger import log
|
|
34
|
+
|
|
35
|
+
from .helper_paths import path_posix
|
|
31
36
|
|
|
32
37
|
|
|
33
38
|
class Compression(str, Enum):
|
|
@@ -49,46 +54,55 @@ class EmulationTask(ShpModel):
|
|
|
49
54
|
|
|
50
55
|
# General config
|
|
51
56
|
input_path: Path
|
|
52
|
-
|
|
57
|
+
""" ⤷ hdf5 file containing harvesting data"""
|
|
53
58
|
output_path: Optional[Path] = None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
""" ⤷ dir- or file-path for storing the recorded data:
|
|
60
|
+
|
|
61
|
+
- providing a directory -> file is named emu_timestamp.h5
|
|
62
|
+
- for a complete path the filename is not changed except it exists and
|
|
63
|
+
overwrite is disabled -> emu#num.h5
|
|
64
|
+
TODO: should the output-path be mandatory?
|
|
65
|
+
"""
|
|
59
66
|
force_overwrite: bool = False
|
|
60
|
-
|
|
67
|
+
""" ⤷ Overwrite existing file"""
|
|
61
68
|
output_compression: Optional[Compression] = Compression.default
|
|
62
|
-
|
|
69
|
+
""" ⤷ should be lzf, 1 (gzip level 1) or None (order of recommendation)"""
|
|
63
70
|
time_start: Optional[datetime] = None
|
|
64
|
-
|
|
71
|
+
""" timestamp or unix epoch time, None = ASAP"""
|
|
65
72
|
duration: Optional[timedelta] = None
|
|
66
|
-
|
|
73
|
+
""" ⤷ Duration of recording in seconds, None = till EOF"""
|
|
67
74
|
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
68
75
|
|
|
69
76
|
# emulation-specific
|
|
70
77
|
use_cal_default: bool = False
|
|
71
|
-
|
|
78
|
+
""" ⤷ Use default calibration values, skip loading from EEPROM"""
|
|
72
79
|
|
|
73
80
|
enable_io: bool = True
|
|
74
81
|
# TODO: add direction of pins! also it seems error-prone when only setting _tracing
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
""" ⤷ Switch the GPIO level converter to targets on/off
|
|
83
|
+
|
|
84
|
+
pre-req for sampling gpio / uart,
|
|
85
|
+
"""
|
|
77
86
|
io_port: TargetPort = TargetPort.A
|
|
78
|
-
|
|
87
|
+
""" ⤷ Either Port A or B that gets connected to IO"""
|
|
79
88
|
pwr_port: TargetPort = TargetPort.A
|
|
80
|
-
|
|
81
|
-
# the other port is aux
|
|
82
|
-
voltage_aux: Union[Annotated[float, Field(ge=0, le=4.5)], str] = 0
|
|
83
|
-
# ⤷ aux_voltage options:
|
|
84
|
-
# - 0-4.5 for specific const Voltage (0 V = disabled),
|
|
85
|
-
# - "buffer" will output intermediate voltage (storage cap of vsource),
|
|
86
|
-
# - "main" will mirror main target voltage
|
|
89
|
+
""" ⤷ selected port will be current-monitored
|
|
87
90
|
|
|
91
|
+
- main channel is nnected to virtual Source
|
|
92
|
+
- the other port is aux
|
|
93
|
+
"""
|
|
94
|
+
voltage_aux: Union[Annotated[float, Field(ge=0, le=4.5)], str] = 0
|
|
95
|
+
""" ⤷ aux_voltage options
|
|
96
|
+
- 0-4.5 for specific const Voltage (0 V = disabled),
|
|
97
|
+
- "buffer" will output intermediate voltage (storage cap of vsource),
|
|
98
|
+
- "main" will mirror main target voltage
|
|
99
|
+
"""
|
|
88
100
|
# sub-elements, could be partly moved to emulation
|
|
89
|
-
virtual_source: VirtualSourceConfig =
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
virtual_source: VirtualSourceConfig = vsrc_neutral
|
|
102
|
+
""" ⤷ Use the desired setting for the virtual source,
|
|
103
|
+
|
|
104
|
+
provide parameters or name like BQ25570
|
|
105
|
+
"""
|
|
92
106
|
|
|
93
107
|
power_tracing: Optional[PowerTracing] = PowerTracing()
|
|
94
108
|
gpio_tracing: Optional[GpioTracing] = GpioTracing()
|
|
@@ -97,7 +111,10 @@ class EmulationTask(ShpModel):
|
|
|
97
111
|
sys_logging: Optional[SystemLogging] = SystemLogging()
|
|
98
112
|
|
|
99
113
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
100
|
-
|
|
114
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug,
|
|
115
|
+
|
|
116
|
+
TODO: just bool now, systemwide
|
|
117
|
+
"""
|
|
101
118
|
|
|
102
119
|
@model_validator(mode="before")
|
|
103
120
|
@classmethod
|
|
@@ -136,9 +153,9 @@ class EmulationTask(ShpModel):
|
|
|
136
153
|
_io is not None for _io in (self.gpio_actuation, self.gpio_tracing, self.uart_logging)
|
|
137
154
|
)
|
|
138
155
|
if self.enable_io and not io_requested:
|
|
139
|
-
|
|
156
|
+
log.warning("Target IO enabled, but no feature requested IO")
|
|
140
157
|
if not self.enable_io and io_requested:
|
|
141
|
-
|
|
158
|
+
log.warning("Target IO not enabled, but a feature requested IO")
|
|
142
159
|
return self
|
|
143
160
|
|
|
144
161
|
@classmethod
|
|
@@ -152,8 +169,8 @@ class EmulationTask(ShpModel):
|
|
|
152
169
|
)
|
|
153
170
|
|
|
154
171
|
return cls(
|
|
155
|
-
input_path=tgt_cfg.energy_env.data_path,
|
|
156
|
-
output_path=root_path / f"emu_{obs.name}.h5",
|
|
172
|
+
input_path=path_posix(tgt_cfg.energy_env.data_path),
|
|
173
|
+
output_path=path_posix(root_path / f"emu_{obs.name}.h5"),
|
|
157
174
|
time_start=copy.copy(xp.time_start),
|
|
158
175
|
duration=xp.duration,
|
|
159
176
|
enable_io=io_requested,
|
|
@@ -167,6 +184,11 @@ class EmulationTask(ShpModel):
|
|
|
167
184
|
sys_logging=xp.sys_logging,
|
|
168
185
|
)
|
|
169
186
|
|
|
187
|
+
def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
|
|
188
|
+
all_ok = any(self.input_path.is_relative_to(path) for path in paths)
|
|
189
|
+
all_ok &= any(self.output_path.is_relative_to(path) for path in paths)
|
|
190
|
+
return all_ok
|
|
191
|
+
|
|
170
192
|
|
|
171
193
|
# TODO: herdConfig
|
|
172
194
|
# - store if path is remote (read & write)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Config for Task that adds the custom ID to the firmware & stores it into a file."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from collections.abc import Set as AbstractSet
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from pathlib import PurePosixPath
|
|
5
6
|
from typing import Annotated
|
|
6
7
|
from typing import Optional
|
|
7
8
|
from typing import TypedDict
|
|
@@ -22,7 +23,9 @@ from shepherd_core.data_models.experiment.experiment import Experiment
|
|
|
22
23
|
from shepherd_core.data_models.testbed import Testbed
|
|
23
24
|
from shepherd_core.data_models.testbed.target import IdInt16
|
|
24
25
|
from shepherd_core.data_models.testbed.target import MCUPort
|
|
25
|
-
from shepherd_core.logger import
|
|
26
|
+
from shepherd_core.logger import log
|
|
27
|
+
|
|
28
|
+
from .helper_paths import path_posix
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class FirmwareModTask(ShpModel):
|
|
@@ -34,7 +37,7 @@ class FirmwareModTask(ShpModel):
|
|
|
34
37
|
firmware_file: Path
|
|
35
38
|
|
|
36
39
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
37
|
-
|
|
40
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
38
41
|
|
|
39
42
|
@model_validator(mode="after")
|
|
40
43
|
def post_validation(self) -> Self:
|
|
@@ -42,7 +45,7 @@ class FirmwareModTask(ShpModel):
|
|
|
42
45
|
FirmwareDType.base64_hex,
|
|
43
46
|
FirmwareDType.path_hex,
|
|
44
47
|
}:
|
|
45
|
-
|
|
48
|
+
log.warning("Firmware is scheduled to get custom-ID but is not in elf-format")
|
|
46
49
|
return self
|
|
47
50
|
|
|
48
51
|
@classmethod
|
|
@@ -68,10 +71,10 @@ class FirmwareModTask(ShpModel):
|
|
|
68
71
|
fw_id = obs.get_target(tgt_id).testbed_id
|
|
69
72
|
|
|
70
73
|
return cls(
|
|
71
|
-
data=fw.data,
|
|
74
|
+
data=path_posix(fw.data) if isinstance(fw.data, Path) else fw.data,
|
|
72
75
|
data_type=fw.data_type,
|
|
73
76
|
custom_id=fw_id,
|
|
74
|
-
firmware_file=
|
|
77
|
+
firmware_file=path_posix(fw_path),
|
|
75
78
|
)
|
|
76
79
|
|
|
77
80
|
@classmethod
|
|
@@ -90,3 +93,9 @@ class FirmwareModTask(ShpModel):
|
|
|
90
93
|
path_new: Path = path / fw.name
|
|
91
94
|
kwargs["firmware_file"] = path_new.with_suffix(".hex")
|
|
92
95
|
return cls(**kwargs)
|
|
96
|
+
|
|
97
|
+
def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
|
|
98
|
+
all_ok = any(self.firmware_file.is_relative_to(path) for path in paths)
|
|
99
|
+
if isinstance(self.data, Path):
|
|
100
|
+
all_ok = any(self.data.is_relative_to(path) for path in paths)
|
|
101
|
+
return all_ok
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Config for the Observer in Harvest-Mode to record IV data from a harvesting-source."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Set as AbstractSet
|
|
3
4
|
from datetime import datetime
|
|
4
5
|
from datetime import timedelta
|
|
5
6
|
from pathlib import Path
|
|
7
|
+
from pathlib import PurePosixPath
|
|
6
8
|
from typing import Annotated
|
|
7
9
|
from typing import Optional
|
|
8
10
|
|
|
@@ -25,34 +27,35 @@ class HarvestTask(ShpModel):
|
|
|
25
27
|
|
|
26
28
|
# General config
|
|
27
29
|
output_path: Path
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
""" ⤷ dir- or file-path for storing the recorded data:
|
|
31
|
+
|
|
32
|
+
- providing a directory -> file is named hrv_timestamp.h5
|
|
33
|
+
- for a complete path the filename is not changed except it exists and
|
|
34
|
+
overwrite is disabled -> name#num.h5
|
|
35
|
+
"""
|
|
32
36
|
force_overwrite: bool = False
|
|
33
|
-
|
|
37
|
+
""" ⤷ Overwrite existing file"""
|
|
34
38
|
output_compression: Optional[Compression] = Compression.default
|
|
35
|
-
|
|
39
|
+
""" ⤷ should be 1 (level 1 gzip), lzf, or None (order of recommendation)"""
|
|
36
40
|
|
|
37
41
|
time_start: Optional[datetime] = None
|
|
38
|
-
|
|
42
|
+
""" timestamp or unix epoch time, None = ASAP"""
|
|
39
43
|
duration: Optional[timedelta] = None
|
|
40
|
-
|
|
44
|
+
""" ⤷ Duration of recording in seconds, None = till EOFSys"""
|
|
41
45
|
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
42
46
|
|
|
43
47
|
# emulation-specific
|
|
44
48
|
use_cal_default: bool = False
|
|
45
|
-
|
|
49
|
+
""" ⤷ Use default calibration values, skip loading from EEPROM"""
|
|
46
50
|
|
|
47
51
|
virtual_harvester: VirtualHarvesterConfig = VirtualHarvesterConfig(name="mppt_opt")
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
""" ⤷ Choose one of the predefined virtual harvesters or configure a new one
|
|
53
|
+
"""
|
|
51
54
|
power_tracing: PowerTracing = PowerTracing()
|
|
52
55
|
sys_logging: Optional[SystemLogging] = SystemLogging()
|
|
53
56
|
|
|
54
57
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
55
|
-
|
|
58
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
56
59
|
|
|
57
60
|
# TODO: there is an unused DAC-Output patched to the harvesting-port
|
|
58
61
|
|
|
@@ -83,3 +86,6 @@ class HarvestTask(ShpModel):
|
|
|
83
86
|
if self.duration and self.duration.total_seconds() < 0:
|
|
84
87
|
raise ValueError("Task-Duration can't be negative.")
|
|
85
88
|
return self
|
|
89
|
+
|
|
90
|
+
def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
|
|
91
|
+
return any(self.output_path.is_relative_to(path) for path in paths)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
r"""Helper FN to avoid unwanted behavior.
|
|
2
|
+
|
|
3
|
+
On windows Path("\xyz") gets transformed to "/xyz", but not on linux.
|
|
4
|
+
When sending an experiment via fastapi, this bug gets triggered.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def path_posix(path: Path) -> Path:
|
|
11
|
+
r"""Help Linux to get from "\xyz" to "/xyz".
|
|
12
|
+
|
|
13
|
+
This isn't a problem on windows and gets triggered when sending XP via fastapi.
|
|
14
|
+
"""
|
|
15
|
+
return Path(path.as_posix().replace("\\", "/"))
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Collection of tasks for selected observer included in experiment."""
|
|
2
2
|
|
|
3
|
+
from collections.abc import Set as AbstractSet
|
|
3
4
|
from datetime import datetime
|
|
4
|
-
from datetime import timedelta
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from pathlib import PurePosixPath
|
|
6
7
|
from typing import Annotated
|
|
7
8
|
from typing import Optional
|
|
8
9
|
|
|
@@ -18,6 +19,7 @@ from shepherd_core.data_models.testbed.testbed import Testbed
|
|
|
18
19
|
|
|
19
20
|
from .emulation import EmulationTask
|
|
20
21
|
from .firmware_mod import FirmwareModTask
|
|
22
|
+
from .helper_paths import path_posix
|
|
21
23
|
from .programming import ProgrammingTask
|
|
22
24
|
|
|
23
25
|
|
|
@@ -25,12 +27,10 @@ class ObserverTasks(ShpModel):
|
|
|
25
27
|
"""Collection of tasks for selected observer included in experiment."""
|
|
26
28
|
|
|
27
29
|
observer: NameStr
|
|
28
|
-
owner_id: Optional[IdInt] # TODO: set to optional for now, shouldn't be
|
|
29
30
|
|
|
30
31
|
# PRE PROCESS
|
|
31
|
-
time_prep: datetime # TODO:
|
|
32
|
+
time_prep: Optional[datetime] = None # TODO: currently not used
|
|
32
33
|
root_path: Path
|
|
33
|
-
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
34
34
|
|
|
35
35
|
# fw mod, store as hex-file and program
|
|
36
36
|
fw1_mod: Optional[FirmwareModTask] = None
|
|
@@ -41,6 +41,10 @@ class ObserverTasks(ShpModel):
|
|
|
41
41
|
# MAIN PROCESS
|
|
42
42
|
emulation: Optional[EmulationTask] = None
|
|
43
43
|
|
|
44
|
+
# deprecations, TODO: remove before public release
|
|
45
|
+
owner_id: Annotated[Optional[IdInt], deprecated("not needed anymore")] = None
|
|
46
|
+
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
47
|
+
|
|
44
48
|
# post_copy / cleanup, Todo: could also just intake emuTask
|
|
45
49
|
# - delete firmwares
|
|
46
50
|
# - decode uart
|
|
@@ -53,25 +57,14 @@ class ObserverTasks(ShpModel):
|
|
|
53
57
|
if not tb.shared_storage:
|
|
54
58
|
raise ValueError("Implementation currently relies on shared storage!")
|
|
55
59
|
|
|
56
|
-
t_start = (
|
|
57
|
-
xp.time_start
|
|
58
|
-
if isinstance(xp.time_start, datetime)
|
|
59
|
-
else datetime.now().astimezone() + timedelta(minutes=3)
|
|
60
|
-
) # TODO: this is messed up, just a hotfix
|
|
61
|
-
|
|
62
60
|
obs = tb.get_observer(tgt_id)
|
|
63
|
-
|
|
64
|
-
root_path = tb.data_on_observer / xp_dir
|
|
65
|
-
# TODO: Paths should be "friendlier"
|
|
66
|
-
# - replace whitespace with "_" and remove non-alphanum?
|
|
67
|
-
|
|
61
|
+
root_path = tb.data_on_observer / "experiments" / xp.folder_name()
|
|
68
62
|
fw_paths = [root_path / f"fw{_i}_{obs.name}.hex" for _i in [1, 2]]
|
|
69
63
|
|
|
70
64
|
return cls(
|
|
71
65
|
observer=obs.name,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
root_path=root_path,
|
|
66
|
+
# time_prep=
|
|
67
|
+
root_path=path_posix(root_path),
|
|
75
68
|
fw1_mod=FirmwareModTask.from_xp(xp, tb, tgt_id, 1, fw_paths[0]),
|
|
76
69
|
fw2_mod=FirmwareModTask.from_xp(xp, tb, tgt_id, 2, fw_paths[1]),
|
|
77
70
|
fw1_prog=ProgrammingTask.from_xp(xp, tb, tgt_id, 1, fw_paths[0]),
|
|
@@ -99,3 +92,12 @@ class ObserverTasks(ShpModel):
|
|
|
99
92
|
raise ValueError("Emu-Task should have a valid output-path")
|
|
100
93
|
values[self.observer] = self.emulation.output_path
|
|
101
94
|
return values
|
|
95
|
+
|
|
96
|
+
def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
|
|
97
|
+
all_ok = any(self.root_path.is_relative_to(path) for path in paths)
|
|
98
|
+
all_ok &= self.fw1_mod.is_contained(paths)
|
|
99
|
+
all_ok &= self.fw2_mod.is_contained(paths)
|
|
100
|
+
all_ok &= self.fw1_prog.is_contained(paths)
|
|
101
|
+
all_ok &= self.fw2_prog.is_contained(paths)
|
|
102
|
+
all_ok &= self.emulation.is_contained(paths)
|
|
103
|
+
return all_ok
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Config for a Task programming the selected target."""
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
from collections.abc import Set as AbstractSet
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from pathlib import PurePosixPath
|
|
5
6
|
from typing import Annotated
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -21,6 +22,8 @@ from shepherd_core.data_models.testbed.mcu import ProgrammerProtocol
|
|
|
21
22
|
from shepherd_core.data_models.testbed.target import MCUPort
|
|
22
23
|
from shepherd_core.data_models.testbed.testbed import Testbed
|
|
23
24
|
|
|
25
|
+
from .helper_paths import path_posix
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
class ProgrammingTask(ShpModel):
|
|
26
29
|
"""Config for a Task programming the selected target."""
|
|
@@ -29,7 +32,7 @@ class ProgrammingTask(ShpModel):
|
|
|
29
32
|
target_port: TargetPort = TargetPort.A
|
|
30
33
|
mcu_port: MCUPort = 1
|
|
31
34
|
mcu_type: SafeStr
|
|
32
|
-
|
|
35
|
+
""" ⤷ must be either "nrf52" or "msp430" ATM, TODO: clean xp to tasks"""
|
|
33
36
|
voltage: Annotated[float, Field(ge=1, lt=5)] = 3
|
|
34
37
|
datarate: Annotated[int, Field(gt=0, le=1_000_000)] = 200_000
|
|
35
38
|
protocol: ProgrammerProtocol
|
|
@@ -38,7 +41,7 @@ class ProgrammingTask(ShpModel):
|
|
|
38
41
|
simulate: bool = False
|
|
39
42
|
|
|
40
43
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
41
|
-
|
|
44
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
42
45
|
|
|
43
46
|
@model_validator(mode="after")
|
|
44
47
|
def post_validation(self) -> Self:
|
|
@@ -66,7 +69,7 @@ class ProgrammingTask(ShpModel):
|
|
|
66
69
|
return None
|
|
67
70
|
|
|
68
71
|
return cls(
|
|
69
|
-
firmware_file=
|
|
72
|
+
firmware_file=path_posix(fw_path),
|
|
70
73
|
target_port=obs.get_target_port(tgt_id),
|
|
71
74
|
mcu_port=mcu_port,
|
|
72
75
|
mcu_type=fw.mcu.name,
|
|
@@ -74,3 +77,6 @@ class ProgrammingTask(ShpModel):
|
|
|
74
77
|
datarate=fw.mcu.prog_datarate,
|
|
75
78
|
protocol=fw.mcu.prog_protocol,
|
|
76
79
|
)
|
|
80
|
+
|
|
81
|
+
def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
|
|
82
|
+
return any(self.firmware_file.is_relative_to(path) for path in paths)
|