shepherd-core 2025.5.2__py3-none-any.whl → 2025.6.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/commons.py +3 -5
- shepherd_core/config.py +34 -0
- shepherd_core/data_models/__init__.py +2 -2
- shepherd_core/data_models/base/calibration.py +13 -8
- shepherd_core/data_models/base/content.py +4 -13
- shepherd_core/data_models/base/wrapper.py +4 -4
- shepherd_core/data_models/content/_external_fixtures.yaml +11 -11
- shepherd_core/data_models/content/energy_environment.py +1 -1
- shepherd_core/data_models/content/firmware.py +10 -5
- shepherd_core/data_models/content/virtual_harvester.py +256 -27
- shepherd_core/data_models/content/virtual_source.py +37 -28
- shepherd_core/data_models/content/virtual_source_fixture.yaml +1 -1
- shepherd_core/data_models/experiment/experiment.py +29 -19
- shepherd_core/data_models/experiment/observer_features.py +64 -28
- shepherd_core/data_models/experiment/target_config.py +19 -9
- shepherd_core/data_models/task/emulation.py +45 -32
- shepherd_core/data_models/task/firmware_mod.py +1 -1
- shepherd_core/data_models/task/harvest.py +16 -14
- shepherd_core/data_models/task/observer_tasks.py +8 -6
- shepherd_core/data_models/task/programming.py +3 -2
- shepherd_core/data_models/task/testbed_tasks.py +7 -9
- shepherd_core/data_models/testbed/cape_fixture.yaml +9 -1
- shepherd_core/data_models/testbed/gpio.py +7 -0
- shepherd_core/data_models/testbed/observer.py +1 -1
- shepherd_core/data_models/testbed/observer_fixture.yaml +19 -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 +14 -1
- shepherd_core/data_models/testbed/testbed.py +8 -9
- shepherd_core/data_models/testbed/testbed_fixture.yaml +11 -0
- shepherd_core/fw_tools/patcher.py +7 -8
- shepherd_core/inventory/system.py +1 -3
- shepherd_core/reader.py +15 -7
- shepherd_core/testbed_client/cache_path.py +1 -1
- shepherd_core/testbed_client/client_web.py +2 -2
- shepherd_core/testbed_client/fixtures.py +13 -11
- shepherd_core/testbed_client/user_model.py +3 -6
- shepherd_core/version.py +1 -1
- shepherd_core/writer.py +2 -2
- {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/METADATA +12 -12
- {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/RECORD +44 -43
- {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/WHEEL +1 -1
- {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/zip-safe +0 -0
|
@@ -7,7 +7,7 @@ from pydantic import Field
|
|
|
7
7
|
from pydantic import model_validator
|
|
8
8
|
from typing_extensions import Self
|
|
9
9
|
|
|
10
|
-
from shepherd_core.
|
|
10
|
+
from shepherd_core.config import config
|
|
11
11
|
from shepherd_core.data_models.base.content import ContentModel
|
|
12
12
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
13
13
|
from shepherd_core.logger import logger
|
|
@@ -23,6 +23,9 @@ NormedNum = Annotated[float, Field(ge=0.0, le=1.0)]
|
|
|
23
23
|
LUT1D = Annotated[list[NormedNum], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]
|
|
24
24
|
LUT2D = Annotated[list[LUT1D], Field(min_length=LUT_SIZE, max_length=LUT_SIZE)]
|
|
25
25
|
|
|
26
|
+
# defaults (pre-init complex types for improved perf) TODO: is documentation still fine?
|
|
27
|
+
vhrv_mppt_opt = VirtualHarvesterConfig(name="mppt_opt")
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
28
31
|
"""The vSrc uses the energy environment (file) for supplying the Target Node.
|
|
@@ -41,45 +44,48 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
41
44
|
# General Metadata & Ownership -> ContentModel
|
|
42
45
|
|
|
43
46
|
enable_boost: bool = False
|
|
44
|
-
|
|
47
|
+
""" ⤷ if false -> v_intermediate = v_input, output-switch-hysteresis is still usable"""
|
|
45
48
|
enable_buck: bool = False
|
|
46
|
-
|
|
49
|
+
""" ⤷ if false -> v_output = v_intermediate"""
|
|
47
50
|
enable_feedback_to_hrv: bool = False
|
|
48
|
-
|
|
51
|
+
""" src can control a cv-harvester for ivcurve"""
|
|
49
52
|
|
|
50
53
|
interval_startup_delay_drain_ms: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
51
54
|
|
|
52
|
-
harvester: VirtualHarvesterConfig =
|
|
55
|
+
harvester: VirtualHarvesterConfig = vhrv_mppt_opt
|
|
53
56
|
|
|
54
57
|
V_input_max_mV: Annotated[float, Field(ge=0, le=10_000)] = 10_000
|
|
55
58
|
I_input_max_mA: Annotated[float, Field(ge=0, le=4.29e3)] = 4_200
|
|
56
59
|
V_input_drop_mV: Annotated[float, Field(ge=0, le=4.29e6)] = 0
|
|
57
|
-
|
|
60
|
+
""" ⤷ simulate input-diode"""
|
|
58
61
|
R_input_mOhm: Annotated[float, Field(ge=0, le=4.29e6)] = 0
|
|
59
|
-
|
|
62
|
+
""" ⤷ resistance only active with disabled boost, range [1 mOhm; 1MOhm]"""
|
|
60
63
|
|
|
61
64
|
# primary storage-Cap
|
|
62
65
|
C_intermediate_uF: Annotated[float, Field(ge=0, le=100_000)] = 0
|
|
63
66
|
V_intermediate_init_mV: Annotated[float, Field(ge=0, le=10_000)] = 3_000
|
|
64
|
-
|
|
67
|
+
""" ⤷ allow a proper / fast startup"""
|
|
65
68
|
I_intermediate_leak_nA: Annotated[float, Field(ge=0, le=4.29e9)] = 0
|
|
66
69
|
|
|
67
70
|
V_intermediate_enable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 1
|
|
68
|
-
|
|
71
|
+
""" ⤷ target gets connected (hysteresis-combo with next value)"""
|
|
69
72
|
V_intermediate_disable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
70
|
-
|
|
73
|
+
""" ⤷ target gets disconnected"""
|
|
71
74
|
interval_check_thresholds_ms: Annotated[float, Field(ge=0, le=4.29e3)] = 0
|
|
72
|
-
|
|
75
|
+
""" ⤷ some ICs (BQ) check every 64 ms if output should be disconnected"""
|
|
76
|
+
# TODO: add intervals for input-disable, output-disable & power-good-signal
|
|
73
77
|
|
|
74
78
|
# pwr-good: target is informed on output-pin (hysteresis) -> for intermediate voltage
|
|
75
79
|
V_pwr_good_enable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 2_800
|
|
76
80
|
V_pwr_good_disable_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 2200
|
|
77
81
|
immediate_pwr_good_signal: bool = True
|
|
78
|
-
|
|
82
|
+
""" ⤷ 1: activate instant schmitt-trigger, 0: stay in interval for checking thresholds"""
|
|
79
83
|
|
|
80
|
-
# final (always last) stage to compensate undetectable current spikes
|
|
81
|
-
# when enabling power for target
|
|
82
84
|
C_output_uF: Annotated[float, Field(ge=0, le=4.29e6)] = 1.0
|
|
85
|
+
"""
|
|
86
|
+
final (always last) stage to compensate undetectable current spikes when
|
|
87
|
+
enabling power for target
|
|
88
|
+
"""
|
|
83
89
|
# TODO: C_output is handled internally as delta-V, but should be a I_transient
|
|
84
90
|
# that makes it visible in simulation as additional i_out_drain
|
|
85
91
|
# TODO: potential weakness, ACD lowpass is capturing transient,
|
|
@@ -87,32 +93,35 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
87
93
|
|
|
88
94
|
# Extra
|
|
89
95
|
V_output_log_gpio_threshold_mV: Annotated[float, Field(ge=0, le=4.29e6)] = 1_400
|
|
90
|
-
|
|
96
|
+
""" ⤷ min voltage needed to enable recording changes in gpio-bank"""
|
|
91
97
|
|
|
92
98
|
# Boost Converter
|
|
93
99
|
V_input_boost_threshold_mV: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
94
|
-
|
|
100
|
+
""" ⤷ min input-voltage for the boost converter to work"""
|
|
95
101
|
V_intermediate_max_mV: Annotated[float, Field(ge=0, le=10_000)] = 10_000
|
|
96
|
-
|
|
102
|
+
""" ⤷ boost converter shuts off"""
|
|
97
103
|
|
|
98
104
|
LUT_input_efficiency: LUT2D = 12 * [12 * [1.00]]
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
""" ⤷ rows are current -> first row a[V=0][:]
|
|
106
|
+
|
|
107
|
+
input-LUT[12][12] depending on array[inp_voltage][log(inp_current)],
|
|
108
|
+
influence of cap-voltage is not implemented
|
|
109
|
+
"""
|
|
110
|
+
|
|
102
111
|
LUT_input_V_min_log2_uV: Annotated[int, Field(ge=0, le=20)] = 0
|
|
103
|
-
|
|
112
|
+
""" ⤷ i.e. 2^7 = 128 uV -> LUT[0][:] is for inputs < 128 uV"""
|
|
104
113
|
LUT_input_I_min_log2_nA: Annotated[int, Field(ge=1, le=20)] = 1
|
|
105
|
-
|
|
114
|
+
""" ⤷ i.e. 2^8 = 256 nA -> LUT[:][0] is for inputs < 256 nA"""
|
|
106
115
|
|
|
107
116
|
# Buck Converter
|
|
108
117
|
V_output_mV: Annotated[float, Field(ge=0, le=5_000)] = 2_400
|
|
109
118
|
V_buck_drop_mV: Annotated[float, Field(ge=0, le=5_000)] = 0
|
|
110
|
-
|
|
119
|
+
""" ⤷ simulate LDO / diode min voltage differential or output-diode"""
|
|
111
120
|
|
|
112
121
|
LUT_output_efficiency: LUT1D = 12 * [1.00]
|
|
113
|
-
|
|
122
|
+
""" ⤷ array[12] depending on output_current"""
|
|
114
123
|
LUT_output_I_min_log2_nA: Annotated[int, Field(ge=1, le=20)] = 1
|
|
115
|
-
|
|
124
|
+
""" ⤷ 2^8 = 256 nA -> LUT[0] is for inputs < 256 nA, see notes on LUT_input for explanation"""
|
|
116
125
|
|
|
117
126
|
@model_validator(mode="before")
|
|
118
127
|
@classmethod
|
|
@@ -241,7 +250,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
241
250
|
dV[uV] = constant[us/nF] * current[nA] = constant[us*V/nAs] * current[nA]
|
|
242
251
|
"""
|
|
243
252
|
C_cap_uF = max(self.C_intermediate_uF, 0.001)
|
|
244
|
-
return int((10**3 * (2**28)) // (C_cap_uF *
|
|
253
|
+
return int((10**3 * (2**28)) // (C_cap_uF * config.SAMPLERATE_SPS))
|
|
245
254
|
|
|
246
255
|
|
|
247
256
|
u32 = Annotated[int, Field(ge=0, lt=2**32)]
|
|
@@ -316,7 +325,7 @@ class ConverterPRUConfig(ShpModel):
|
|
|
316
325
|
dtype_in, log_intermediate_node=log_intermediate_node
|
|
317
326
|
),
|
|
318
327
|
interval_startup_delay_drain_n=round(
|
|
319
|
-
data.interval_startup_delay_drain_ms *
|
|
328
|
+
data.interval_startup_delay_drain_ms * config.SAMPLERATE_SPS * 1e-3
|
|
320
329
|
),
|
|
321
330
|
V_input_max_uV=round(data.V_input_max_mV * 1e3),
|
|
322
331
|
I_input_max_nA=round(data.I_input_max_mA * 1e6),
|
|
@@ -329,7 +338,7 @@ class ConverterPRUConfig(ShpModel):
|
|
|
329
338
|
V_disable_output_threshold_uV=round(states["V_disable_output_threshold_mV"] * 1e3),
|
|
330
339
|
dV_enable_output_uV=round(states["dV_enable_output_mV"] * 1e3),
|
|
331
340
|
interval_check_thresholds_n=round(
|
|
332
|
-
data.interval_check_thresholds_ms *
|
|
341
|
+
data.interval_check_thresholds_ms * config.SAMPLERATE_SPS * 1e-3
|
|
333
342
|
),
|
|
334
343
|
V_pwr_good_enable_threshold_uV=round(data.V_pwr_good_enable_threshold_mV * 1e3),
|
|
335
344
|
V_pwr_good_disable_threshold_uV=round(data.V_pwr_good_disable_threshold_mV * 1e3),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# info:
|
|
2
2
|
# - compendium of all parameters & description
|
|
3
|
-
# - base for neutral fallback values if provided
|
|
3
|
+
# - base for neutral fallback values if provided yaml is sparse
|
|
4
4
|
# - -> it is encouraged to omit redundant parameters
|
|
5
5
|
---
|
|
6
6
|
- datatype: VirtualSourceConfig
|
|
@@ -5,18 +5,18 @@ from datetime import datetime
|
|
|
5
5
|
from datetime import timedelta
|
|
6
6
|
from typing import Annotated
|
|
7
7
|
from typing import Optional
|
|
8
|
-
from typing import Union
|
|
9
|
-
from uuid import uuid4
|
|
10
8
|
|
|
11
|
-
from pydantic import UUID4
|
|
12
9
|
from pydantic import Field
|
|
13
10
|
from pydantic import model_validator
|
|
14
11
|
from typing_extensions import Self
|
|
12
|
+
from typing_extensions import deprecated
|
|
15
13
|
|
|
14
|
+
from shepherd_core.config import config
|
|
16
15
|
from shepherd_core.data_models.base.content import IdInt
|
|
17
16
|
from shepherd_core.data_models.base.content import NameStr
|
|
18
17
|
from shepherd_core.data_models.base.content import SafeStr
|
|
19
18
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
19
|
+
from shepherd_core.data_models.base.timezone import local_now
|
|
20
20
|
from shepherd_core.data_models.testbed.target import Target
|
|
21
21
|
from shepherd_core.data_models.testbed.testbed import Testbed
|
|
22
22
|
from shepherd_core.version import version
|
|
@@ -24,34 +24,28 @@ from shepherd_core.version import version
|
|
|
24
24
|
from .observer_features import SystemLogging
|
|
25
25
|
from .target_config import TargetConfig
|
|
26
26
|
|
|
27
|
+
# defaults (pre-init complex types)
|
|
28
|
+
sys_log_all = SystemLogging() # = all active
|
|
29
|
+
|
|
27
30
|
|
|
28
31
|
class Experiment(ShpModel, title="Config of an Experiment"):
|
|
29
32
|
"""Config for experiments on the testbed emulating energy environments for target nodes."""
|
|
30
33
|
|
|
31
34
|
# General Properties
|
|
32
|
-
# id: UUID4 ... # TODO: db-migration - temp fix for documentation
|
|
33
|
-
id: Union[UUID4, int] = Field(default_factory=uuid4)
|
|
34
|
-
# ⤷ TODO: automatic ID is problematic for identification by hash
|
|
35
|
-
|
|
36
35
|
name: NameStr
|
|
37
36
|
description: Annotated[
|
|
38
37
|
Optional[SafeStr], Field(description="Required for public instances")
|
|
39
38
|
] = None
|
|
40
39
|
comment: Optional[SafeStr] = None
|
|
41
|
-
created: datetime = Field(default_factory=datetime.now)
|
|
42
|
-
|
|
43
|
-
# Ownership & Access
|
|
44
|
-
owner_id: Optional[IdInt] = None
|
|
45
40
|
|
|
46
41
|
# feedback
|
|
47
|
-
email_results: bool =
|
|
42
|
+
email_results: bool = True
|
|
48
43
|
|
|
49
|
-
sys_logging: SystemLogging =
|
|
44
|
+
sys_logging: SystemLogging = sys_log_all
|
|
50
45
|
|
|
51
46
|
# schedule
|
|
52
47
|
time_start: Optional[datetime] = None # = ASAP
|
|
53
48
|
duration: Optional[timedelta] = None # = till EOF
|
|
54
|
-
abort_on_error: bool = False
|
|
55
49
|
|
|
56
50
|
# targets
|
|
57
51
|
target_configs: Annotated[list[TargetConfig], Field(min_length=1, max_length=128)]
|
|
@@ -59,11 +53,16 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
59
53
|
# debug
|
|
60
54
|
lib_ver: Optional[str] = version
|
|
61
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
|
|
61
|
+
|
|
62
62
|
@model_validator(mode="after")
|
|
63
63
|
def post_validation(self) -> Self:
|
|
64
|
-
|
|
64
|
+
self._validate_observers(self.target_configs)
|
|
65
65
|
self._validate_targets(self.target_configs)
|
|
66
|
-
self._validate_observers(self.target_configs, testbed)
|
|
67
66
|
if self.duration and self.duration.total_seconds() < 0:
|
|
68
67
|
raise ValueError("Duration of experiment can't be negative.")
|
|
69
68
|
return self
|
|
@@ -75,8 +74,9 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
75
74
|
for _config in configs:
|
|
76
75
|
for _id in _config.target_IDs:
|
|
77
76
|
target_ids.append(_id)
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
if config.VALIDATE_INFRA:
|
|
78
|
+
Target(id=_id)
|
|
79
|
+
# ⤷ this can raise exception for non-existing targets
|
|
80
80
|
if _config.custom_IDs is not None:
|
|
81
81
|
custom_ids = custom_ids + _config.custom_IDs[: len(_config.target_IDs)]
|
|
82
82
|
else:
|
|
@@ -87,7 +87,10 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
87
87
|
raise ValueError("Custom Target-ID are faulty (some form of id-collisions)!")
|
|
88
88
|
|
|
89
89
|
@staticmethod
|
|
90
|
-
def _validate_observers(configs: Iterable[TargetConfig]
|
|
90
|
+
def _validate_observers(configs: Iterable[TargetConfig]) -> None:
|
|
91
|
+
if not config.VALIDATE_INFRA:
|
|
92
|
+
return
|
|
93
|
+
testbed = Testbed()
|
|
91
94
|
target_ids = [_id for _config in configs for _id in _config.target_IDs]
|
|
92
95
|
obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
|
|
93
96
|
if len(target_ids) > len(set(obs_ids)):
|
|
@@ -105,3 +108,10 @@ class Experiment(ShpModel, title="Config of an Experiment"):
|
|
|
105
108
|
# gets already caught in target_config - but keep:
|
|
106
109
|
msg = f"Target-ID {target_id} was not found in Experiment '{self.name}'"
|
|
107
110
|
raise ValueError(msg)
|
|
111
|
+
|
|
112
|
+
def folder_name(self, custom_date: Optional[datetime] = None) -> str:
|
|
113
|
+
date = custom_date if custom_date is not None else self.time_start
|
|
114
|
+
timestamp = local_now() if date is None else date
|
|
115
|
+
timestrng = timestamp.strftime("%Y-%m-%d_%H-%M-%S")
|
|
116
|
+
# ⤷ closest to ISO 8601, avoids ":"
|
|
117
|
+
return f"{timestrng}_{self.name.replace(' ', '_')}"
|
|
@@ -6,15 +6,20 @@ from typing import Annotated
|
|
|
6
6
|
from typing import Optional
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
|
+
from annotated_types import Interval
|
|
9
10
|
from pydantic import Field
|
|
10
11
|
from pydantic import PositiveFloat
|
|
11
12
|
from pydantic import model_validator
|
|
12
13
|
from typing_extensions import Self
|
|
13
14
|
from typing_extensions import deprecated
|
|
14
15
|
|
|
16
|
+
from shepherd_core import logger
|
|
15
17
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
16
18
|
from shepherd_core.data_models.testbed.gpio import GPIO
|
|
17
19
|
|
|
20
|
+
# defaults (pre-init complex types)
|
|
21
|
+
zero_duration = timedelta(seconds=0)
|
|
22
|
+
|
|
18
23
|
|
|
19
24
|
class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
20
25
|
"""Configuration for recording the Power-Consumption of the Target Nodes.
|
|
@@ -23,19 +28,27 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
|
|
|
23
28
|
"""
|
|
24
29
|
|
|
25
30
|
intermediate_voltage: bool = False
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
"""
|
|
32
|
+
⤷ for EMU: record storage capacitor instead of output (good for V_out = const)
|
|
33
|
+
this also includes current!
|
|
34
|
+
"""
|
|
29
35
|
# time
|
|
30
|
-
delay: timedelta =
|
|
36
|
+
delay: timedelta = zero_duration
|
|
37
|
+
"""start recording after experiment started"""
|
|
31
38
|
duration: Optional[timedelta] = None # till EOF
|
|
39
|
+
"""duration of recording after delay starts the process.
|
|
40
|
+
|
|
41
|
+
default is None, recording till EOF"""
|
|
32
42
|
|
|
33
43
|
# post-processing
|
|
34
44
|
calculate_power: bool = False
|
|
35
|
-
|
|
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"""
|
|
36
48
|
discard_current: bool = False
|
|
49
|
+
""" ⤷ reduce file-size by omitting current -> not implemented ATM"""
|
|
37
50
|
discard_voltage: bool = False
|
|
38
|
-
|
|
51
|
+
""" ⤷ reduce file-size by omitting voltage -> not implemented ATM"""
|
|
39
52
|
|
|
40
53
|
@model_validator(mode="after")
|
|
41
54
|
def post_validation(self) -> Self:
|
|
@@ -105,7 +118,7 @@ STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
|
|
|
105
118
|
STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
|
|
106
119
|
|
|
107
120
|
|
|
108
|
-
class
|
|
121
|
+
class UartLogging(ShpModel, title="Config for UART Logging"):
|
|
109
122
|
"""Configuration for recording UART-Output of the Target Nodes.
|
|
110
123
|
|
|
111
124
|
Note that the Communication has to be on a specific port that
|
|
@@ -132,49 +145,65 @@ class UartTracing(ShpModel, title="Config for UART Tracing"):
|
|
|
132
145
|
return self
|
|
133
146
|
|
|
134
147
|
|
|
148
|
+
GpioInt = Annotated[int, Interval(ge=0, le=17)]
|
|
149
|
+
GpioList = Annotated[list[GpioInt], Field(min_length=1, max_length=18)]
|
|
150
|
+
all_gpio = list(range(18))
|
|
151
|
+
|
|
152
|
+
|
|
135
153
|
class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
|
|
136
154
|
"""Configuration for recording the GPIO-Output of the Target Nodes.
|
|
137
155
|
|
|
138
156
|
TODO: postprocessing not implemented ATM
|
|
139
157
|
"""
|
|
140
158
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
159
|
+
gpios: GpioList = all_gpio
|
|
160
|
+
"""List of GPIO to record.
|
|
161
|
+
|
|
162
|
+
This feature allows to remove unwanted pins from recording,
|
|
163
|
+
i.e. for chatty pins with separate UART Logging enabled.
|
|
164
|
+
Numbering is based on the Target-Port and its 16x GPIO and two PwrGood-Signals.
|
|
165
|
+
See doc for nRF_FRAM_Target_v1.3+ to see mapping of target port.
|
|
166
|
+
|
|
167
|
+
Example for skipping UART (pin 0 & 1):
|
|
168
|
+
.gpio = range(2,18)
|
|
169
|
+
|
|
170
|
+
Note:
|
|
171
|
+
- Cape 2.4 (2023) and lower only has 9x GPIO + 1x PwrGood
|
|
172
|
+
- Cape 2.5 (2025) has first 12 GPIO & both PwrGood
|
|
173
|
+
- this will be mapped accordingly by the observer
|
|
174
|
+
"""
|
|
146
175
|
|
|
147
176
|
# time
|
|
148
|
-
delay: timedelta =
|
|
177
|
+
delay: timedelta = zero_duration
|
|
149
178
|
duration: Optional[timedelta] = None # till EOF
|
|
150
179
|
|
|
151
180
|
# post-processing,
|
|
152
181
|
uart_decode: bool = False
|
|
153
|
-
|
|
154
|
-
# NOTE: gpio-tracing currently shows rather big - but rare - "blind" windows (~1-4us)
|
|
182
|
+
"""Automatic decoding from gpio-trace not implemented ATM."""
|
|
155
183
|
uart_pin: GPIO = GPIO(name="GPIO8")
|
|
156
184
|
uart_baudrate: Annotated[int, Field(ge=2_400, le=1_152_000)] = 115_200
|
|
157
|
-
# TODO: add a "discard_gpio" (if only uart is wanted)
|
|
158
185
|
|
|
159
186
|
@model_validator(mode="after")
|
|
160
187
|
def post_validation(self) -> Self:
|
|
161
|
-
if self.mask == 0:
|
|
162
|
-
raise ValueError("Error in config -> tracing enabled but mask is 0")
|
|
163
188
|
if self.delay and self.delay.total_seconds() < 0:
|
|
164
189
|
raise ValueError("Delay can't be negative.")
|
|
165
190
|
if self.duration and self.duration.total_seconds() < 0:
|
|
166
191
|
raise ValueError("Duration can't be negative.")
|
|
167
|
-
if self.mask != 0b11_1111_1111: # GpioTracing.mask
|
|
168
|
-
raise NotImplementedError("Feature GpioTracing.mask reserved for future use.")
|
|
169
|
-
if self.gpios is not None:
|
|
170
|
-
raise NotImplementedError("Feature GpioTracing.gpios reserved for future use.")
|
|
171
192
|
if self.uart_decode:
|
|
172
|
-
|
|
193
|
+
logger.error(
|
|
173
194
|
"Feature GpioTracing.uart_decode reserved for future use. "
|
|
174
|
-
"Use
|
|
195
|
+
"Use UartLogging or manually decode serial with the provided waveform decoder."
|
|
175
196
|
)
|
|
176
197
|
return self
|
|
177
198
|
|
|
199
|
+
@property
|
|
200
|
+
def gpio_mask(self) -> int:
|
|
201
|
+
# valid for cape v2.5
|
|
202
|
+
mask = 0
|
|
203
|
+
for gpio in set(self.gpios):
|
|
204
|
+
mask |= 2**gpio
|
|
205
|
+
return mask
|
|
206
|
+
|
|
178
207
|
|
|
179
208
|
class GpioLevel(str, Enum):
|
|
180
209
|
"""Options for setting the gpio-level or state."""
|
|
@@ -188,12 +217,14 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
|
|
|
188
217
|
"""Configuration for a single GPIO-Event (Actuation)."""
|
|
189
218
|
|
|
190
219
|
delay: PositiveFloat
|
|
191
|
-
|
|
192
|
-
|
|
220
|
+
""" ⤷ from start_time
|
|
221
|
+
|
|
222
|
+
- resolution 10 us (guaranteed, but finer steps are possible)
|
|
223
|
+
"""
|
|
193
224
|
gpio: GPIO
|
|
194
225
|
level: GpioLevel
|
|
195
226
|
period: Annotated[float, Field(ge=10e-6)] = 1
|
|
196
|
-
|
|
227
|
+
""" ⤷ time base of periodicity in s"""
|
|
197
228
|
count: Annotated[int, Field(ge=1, le=4096)] = 1
|
|
198
229
|
|
|
199
230
|
@model_validator(mode="after")
|
|
@@ -216,6 +247,11 @@ class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
|
|
|
216
247
|
|
|
217
248
|
events: Annotated[list[GpioEvent], Field(min_length=1, max_length=1024)]
|
|
218
249
|
|
|
250
|
+
@model_validator(mode="after")
|
|
251
|
+
def post_validation(self) -> Self:
|
|
252
|
+
msg = "not implemented ATM"
|
|
253
|
+
raise ValueError(msg)
|
|
254
|
+
|
|
219
255
|
def get_gpios(self) -> set:
|
|
220
256
|
return {_ev.gpio for _ev in self.events}
|
|
221
257
|
|
|
@@ -228,7 +264,7 @@ class SystemLogging(ShpModel, title="Config for System-Logging"):
|
|
|
228
264
|
sheep: bool = True
|
|
229
265
|
sys_util: bool = True
|
|
230
266
|
|
|
231
|
-
# TODO: remove lines below
|
|
267
|
+
# deprecated, TODO: remove lines below before public release
|
|
232
268
|
dmesg: Annotated[bool, deprecated("for sheep v0.9.0+, use 'kernel' instead")] = True
|
|
233
269
|
ptp: Annotated[bool, deprecated("for sheep v0.9.0+, use 'time_sync' instead")] = True
|
|
234
270
|
shepherd: Annotated[bool, deprecated("for sheep v0.9.0+, use 'sheep' instead")] = True
|
|
@@ -18,7 +18,10 @@ from shepherd_core.data_models.testbed.target import Target
|
|
|
18
18
|
from .observer_features import GpioActuation
|
|
19
19
|
from .observer_features import GpioTracing
|
|
20
20
|
from .observer_features import PowerTracing
|
|
21
|
-
from .observer_features import
|
|
21
|
+
from .observer_features import UartLogging
|
|
22
|
+
|
|
23
|
+
# defaults (pre-init complex types)
|
|
24
|
+
vsrc_neutral = VirtualSourceConfig(name="neutral")
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class TargetConfig(ShpModel, title="Target Config"):
|
|
@@ -26,25 +29,32 @@ class TargetConfig(ShpModel, title="Target Config"):
|
|
|
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
|
|
46
|
-
uart_tracing: Optional[UartTracing] = None
|
|
47
56
|
gpio_actuation: Optional[GpioActuation] = None
|
|
57
|
+
uart_logging: Optional[UartLogging] = None
|
|
48
58
|
|
|
49
59
|
@model_validator(mode="after")
|
|
50
60
|
def post_validation(self) -> Self:
|
|
@@ -13,6 +13,7 @@ from pydantic import Field
|
|
|
13
13
|
from pydantic import model_validator
|
|
14
14
|
from pydantic import validate_call
|
|
15
15
|
from typing_extensions import Self
|
|
16
|
+
from typing_extensions import deprecated
|
|
16
17
|
|
|
17
18
|
from shepherd_core.data_models.base.content import IdInt
|
|
18
19
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
@@ -23,7 +24,8 @@ from shepherd_core.data_models.experiment.observer_features import GpioActuation
|
|
|
23
24
|
from shepherd_core.data_models.experiment.observer_features import GpioTracing
|
|
24
25
|
from shepherd_core.data_models.experiment.observer_features import PowerTracing
|
|
25
26
|
from shepherd_core.data_models.experiment.observer_features import SystemLogging
|
|
26
|
-
from shepherd_core.data_models.experiment.observer_features import
|
|
27
|
+
from shepherd_core.data_models.experiment.observer_features import UartLogging
|
|
28
|
+
from shepherd_core.data_models.experiment.target_config import vsrc_neutral
|
|
27
29
|
from shepherd_core.data_models.testbed import Testbed
|
|
28
30
|
from shepherd_core.data_models.testbed.cape import TargetPort
|
|
29
31
|
from shepherd_core.logger import logger
|
|
@@ -48,55 +50,67 @@ class EmulationTask(ShpModel):
|
|
|
48
50
|
|
|
49
51
|
# General config
|
|
50
52
|
input_path: Path
|
|
51
|
-
|
|
53
|
+
""" ⤷ hdf5 file containing harvesting data"""
|
|
52
54
|
output_path: Optional[Path] = None
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
""" ⤷ dir- or file-path for storing the recorded data:
|
|
56
|
+
|
|
57
|
+
- providing a directory -> file is named emu_timestamp.h5
|
|
58
|
+
- for a complete path the filename is not changed except it exists and
|
|
59
|
+
overwrite is disabled -> emu#num.h5
|
|
60
|
+
TODO: should the output-path be mandatory?
|
|
61
|
+
"""
|
|
58
62
|
force_overwrite: bool = False
|
|
59
|
-
|
|
63
|
+
""" ⤷ Overwrite existing file"""
|
|
60
64
|
output_compression: Optional[Compression] = Compression.default
|
|
61
|
-
|
|
65
|
+
""" ⤷ should be lzf, 1 (gzip level 1) or None (order of recommendation)"""
|
|
62
66
|
time_start: Optional[datetime] = None
|
|
63
|
-
|
|
67
|
+
""" timestamp or unix epoch time, None = ASAP"""
|
|
64
68
|
duration: Optional[timedelta] = None
|
|
65
|
-
|
|
66
|
-
abort_on_error: bool
|
|
69
|
+
""" ⤷ Duration of recording in seconds, None = till EOF"""
|
|
70
|
+
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
67
71
|
|
|
68
72
|
# emulation-specific
|
|
69
73
|
use_cal_default: bool = False
|
|
70
|
-
|
|
74
|
+
""" ⤷ Use default calibration values, skip loading from EEPROM"""
|
|
71
75
|
|
|
72
76
|
enable_io: bool = True
|
|
73
77
|
# TODO: add direction of pins! also it seems error-prone when only setting _tracing
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
""" ⤷ Switch the GPIO level converter to targets on/off
|
|
79
|
+
|
|
80
|
+
pre-req for sampling gpio / uart,
|
|
81
|
+
"""
|
|
76
82
|
io_port: TargetPort = TargetPort.A
|
|
77
|
-
|
|
83
|
+
""" ⤷ Either Port A or B that gets connected to IO"""
|
|
78
84
|
pwr_port: TargetPort = TargetPort.A
|
|
79
|
-
|
|
80
|
-
# the other port is aux
|
|
81
|
-
voltage_aux: Union[Annotated[float, Field(ge=0, le=4.5)], str] = 0
|
|
82
|
-
# ⤷ aux_voltage options:
|
|
83
|
-
# - 0-4.5 for specific const Voltage (0 V = disabled),
|
|
84
|
-
# - "buffer" will output intermediate voltage (storage cap of vsource),
|
|
85
|
-
# - "main" will mirror main target voltage
|
|
85
|
+
""" ⤷ selected port will be current-monitored
|
|
86
86
|
|
|
87
|
+
- main channel is nnected to virtual Source
|
|
88
|
+
- the other port is aux
|
|
89
|
+
"""
|
|
90
|
+
voltage_aux: Union[Annotated[float, Field(ge=0, le=4.5)], str] = 0
|
|
91
|
+
""" ⤷ aux_voltage options
|
|
92
|
+
- 0-4.5 for specific const Voltage (0 V = disabled),
|
|
93
|
+
- "buffer" will output intermediate voltage (storage cap of vsource),
|
|
94
|
+
- "main" will mirror main target voltage
|
|
95
|
+
"""
|
|
87
96
|
# sub-elements, could be partly moved to emulation
|
|
88
|
-
virtual_source: VirtualSourceConfig =
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
virtual_source: VirtualSourceConfig = vsrc_neutral
|
|
98
|
+
""" ⤷ Use the desired setting for the virtual source,
|
|
99
|
+
|
|
100
|
+
provide parameters or name like BQ25570
|
|
101
|
+
"""
|
|
91
102
|
|
|
92
103
|
power_tracing: Optional[PowerTracing] = PowerTracing()
|
|
93
104
|
gpio_tracing: Optional[GpioTracing] = GpioTracing()
|
|
94
|
-
|
|
105
|
+
uart_logging: Optional[UartLogging] = UartLogging()
|
|
95
106
|
gpio_actuation: Optional[GpioActuation] = None
|
|
96
107
|
sys_logging: Optional[SystemLogging] = SystemLogging()
|
|
97
108
|
|
|
98
109
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
99
|
-
|
|
110
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug,
|
|
111
|
+
|
|
112
|
+
TODO: just bool now, systemwide
|
|
113
|
+
"""
|
|
100
114
|
|
|
101
115
|
@model_validator(mode="before")
|
|
102
116
|
@classmethod
|
|
@@ -132,7 +146,7 @@ class EmulationTask(ShpModel):
|
|
|
132
146
|
raise ValueError("GPIO Actuation not yet implemented!")
|
|
133
147
|
|
|
134
148
|
io_requested = any(
|
|
135
|
-
_io is not None for _io in (self.gpio_actuation, self.gpio_tracing, self.
|
|
149
|
+
_io is not None for _io in (self.gpio_actuation, self.gpio_tracing, self.uart_logging)
|
|
136
150
|
)
|
|
137
151
|
if self.enable_io and not io_requested:
|
|
138
152
|
logger.warning("Target IO enabled, but no feature requested IO")
|
|
@@ -147,7 +161,7 @@ class EmulationTask(ShpModel):
|
|
|
147
161
|
tgt_cfg = xp.get_target_config(tgt_id)
|
|
148
162
|
io_requested = any(
|
|
149
163
|
_io is not None
|
|
150
|
-
for _io in (tgt_cfg.gpio_actuation, tgt_cfg.gpio_tracing, tgt_cfg.
|
|
164
|
+
for _io in (tgt_cfg.gpio_actuation, tgt_cfg.gpio_tracing, tgt_cfg.uart_logging)
|
|
151
165
|
)
|
|
152
166
|
|
|
153
167
|
return cls(
|
|
@@ -155,14 +169,13 @@ class EmulationTask(ShpModel):
|
|
|
155
169
|
output_path=root_path / f"emu_{obs.name}.h5",
|
|
156
170
|
time_start=copy.copy(xp.time_start),
|
|
157
171
|
duration=xp.duration,
|
|
158
|
-
abort_on_error=xp.abort_on_error,
|
|
159
172
|
enable_io=io_requested,
|
|
160
173
|
io_port=obs.get_target_port(tgt_id),
|
|
161
174
|
pwr_port=obs.get_target_port(tgt_id),
|
|
162
175
|
virtual_source=tgt_cfg.virtual_source,
|
|
163
176
|
power_tracing=tgt_cfg.power_tracing,
|
|
164
177
|
gpio_tracing=tgt_cfg.gpio_tracing,
|
|
165
|
-
|
|
178
|
+
uart_logging=tgt_cfg.uart_logging,
|
|
166
179
|
gpio_actuation=tgt_cfg.gpio_actuation,
|
|
167
180
|
sys_logging=xp.sys_logging,
|
|
168
181
|
)
|