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
|
@@ -34,7 +34,7 @@ class FirmwareModTask(ShpModel):
|
|
|
34
34
|
firmware_file: Path
|
|
35
35
|
|
|
36
36
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
37
|
-
|
|
37
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
38
38
|
|
|
39
39
|
@model_validator(mode="after")
|
|
40
40
|
def post_validation(self) -> Self:
|
|
@@ -9,6 +9,7 @@ from typing import Optional
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
from pydantic import model_validator
|
|
11
11
|
from typing_extensions import Self
|
|
12
|
+
from typing_extensions import deprecated
|
|
12
13
|
|
|
13
14
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
14
15
|
from shepherd_core.data_models.base.timezone import local_tz
|
|
@@ -24,34 +25,35 @@ class HarvestTask(ShpModel):
|
|
|
24
25
|
|
|
25
26
|
# General config
|
|
26
27
|
output_path: Path
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
""" ⤷ dir- or file-path for storing the recorded data:
|
|
29
|
+
|
|
30
|
+
- providing a directory -> file is named hrv_timestamp.h5
|
|
31
|
+
- for a complete path the filename is not changed except it exists and
|
|
32
|
+
overwrite is disabled -> name#num.h5
|
|
33
|
+
"""
|
|
31
34
|
force_overwrite: bool = False
|
|
32
|
-
|
|
35
|
+
""" ⤷ Overwrite existing file"""
|
|
33
36
|
output_compression: Optional[Compression] = Compression.default
|
|
34
|
-
|
|
37
|
+
""" ⤷ should be 1 (level 1 gzip), lzf, or None (order of recommendation)"""
|
|
35
38
|
|
|
36
39
|
time_start: Optional[datetime] = None
|
|
37
|
-
|
|
40
|
+
""" timestamp or unix epoch time, None = ASAP"""
|
|
38
41
|
duration: Optional[timedelta] = None
|
|
39
|
-
|
|
40
|
-
abort_on_error: bool = False
|
|
42
|
+
""" ⤷ Duration of recording in seconds, None = till EOFSys"""
|
|
43
|
+
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
41
44
|
|
|
42
45
|
# emulation-specific
|
|
43
46
|
use_cal_default: bool = False
|
|
44
|
-
|
|
47
|
+
""" ⤷ Use default calibration values, skip loading from EEPROM"""
|
|
45
48
|
|
|
46
49
|
virtual_harvester: VirtualHarvesterConfig = VirtualHarvesterConfig(name="mppt_opt")
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
""" ⤷ Choose one of the predefined virtual harvesters or configure a new one
|
|
51
|
+
"""
|
|
50
52
|
power_tracing: PowerTracing = PowerTracing()
|
|
51
53
|
sys_logging: Optional[SystemLogging] = SystemLogging()
|
|
52
54
|
|
|
53
55
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
54
|
-
|
|
56
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
55
57
|
|
|
56
58
|
# TODO: there is an unused DAC-Output patched to the harvesting-port
|
|
57
59
|
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from datetime import timedelta
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
8
9
|
from pydantic import validate_call
|
|
9
10
|
from typing_extensions import Self
|
|
11
|
+
from typing_extensions import deprecated
|
|
10
12
|
|
|
11
13
|
from shepherd_core.data_models.base.content import IdInt
|
|
12
14
|
from shepherd_core.data_models.base.content import NameStr
|
|
@@ -23,12 +25,10 @@ class ObserverTasks(ShpModel):
|
|
|
23
25
|
"""Collection of tasks for selected observer included in experiment."""
|
|
24
26
|
|
|
25
27
|
observer: NameStr
|
|
26
|
-
owner_id: Optional[IdInt] # TODO: set to optional for now, shouldn't be
|
|
27
28
|
|
|
28
29
|
# PRE PROCESS
|
|
29
30
|
time_prep: datetime # TODO: should be optional
|
|
30
31
|
root_path: Path
|
|
31
|
-
abort_on_error: bool
|
|
32
32
|
|
|
33
33
|
# fw mod, store as hex-file and program
|
|
34
34
|
fw1_mod: Optional[FirmwareModTask] = None
|
|
@@ -39,6 +39,10 @@ class ObserverTasks(ShpModel):
|
|
|
39
39
|
# MAIN PROCESS
|
|
40
40
|
emulation: Optional[EmulationTask] = None
|
|
41
41
|
|
|
42
|
+
# deprecations, TODO: remove before public release
|
|
43
|
+
owner_id: Annotated[Optional[IdInt], deprecated("not needed anymore")] = None
|
|
44
|
+
abort_on_error: Annotated[bool, deprecated("has no effect")] = False
|
|
45
|
+
|
|
42
46
|
# post_copy / cleanup, Todo: could also just intake emuTask
|
|
43
47
|
# - delete firmwares
|
|
44
48
|
# - decode uart
|
|
@@ -67,10 +71,8 @@ class ObserverTasks(ShpModel):
|
|
|
67
71
|
|
|
68
72
|
return cls(
|
|
69
73
|
observer=obs.name,
|
|
70
|
-
owner_id=xp.owner_id,
|
|
71
74
|
time_prep=t_start - tb.prep_duration,
|
|
72
75
|
root_path=root_path,
|
|
73
|
-
abort_on_error=xp.abort_on_error,
|
|
74
76
|
fw1_mod=FirmwareModTask.from_xp(xp, tb, tgt_id, 1, fw_paths[0]),
|
|
75
77
|
fw2_mod=FirmwareModTask.from_xp(xp, tb, tgt_id, 2, fw_paths[1]),
|
|
76
78
|
fw1_prog=ProgrammingTask.from_xp(xp, tb, tgt_id, 1, fw_paths[0]),
|
|
@@ -91,8 +93,8 @@ class ObserverTasks(ShpModel):
|
|
|
91
93
|
tasks.append(task)
|
|
92
94
|
return tasks
|
|
93
95
|
|
|
94
|
-
def get_output_paths(self) -> dict:
|
|
95
|
-
values = {}
|
|
96
|
+
def get_output_paths(self) -> dict[str, Path]:
|
|
97
|
+
values: dict[str, Path] = {}
|
|
96
98
|
if isinstance(self.emulation, EmulationTask):
|
|
97
99
|
if self.emulation.output_path is None:
|
|
98
100
|
raise ValueError("Emu-Task should have a valid output-path")
|
|
@@ -29,15 +29,16 @@ class ProgrammingTask(ShpModel):
|
|
|
29
29
|
target_port: TargetPort = TargetPort.A
|
|
30
30
|
mcu_port: MCUPort = 1
|
|
31
31
|
mcu_type: SafeStr
|
|
32
|
-
|
|
32
|
+
""" ⤷ must be either "nrf52" or "msp430" ATM, TODO: clean xp to tasks"""
|
|
33
33
|
voltage: Annotated[float, Field(ge=1, lt=5)] = 3
|
|
34
34
|
datarate: Annotated[int, Field(gt=0, le=1_000_000)] = 200_000
|
|
35
35
|
protocol: ProgrammerProtocol
|
|
36
|
+
# TODO: eradicate - should not exist. derive protocol from mcu_type
|
|
36
37
|
|
|
37
38
|
simulate: bool = False
|
|
38
39
|
|
|
39
40
|
verbose: Annotated[int, Field(ge=0, le=4)] = 2
|
|
40
|
-
|
|
41
|
+
""" ⤷ 0=Errors, 1=Warnings, 2=Info, 3=Debug"""
|
|
41
42
|
|
|
42
43
|
@model_validator(mode="after")
|
|
43
44
|
def post_validation(self) -> Self:
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Collection of tasks for all observers included in experiment."""
|
|
2
2
|
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
from typing import Annotated
|
|
4
5
|
from typing import Optional
|
|
5
6
|
|
|
6
7
|
from pydantic import Field
|
|
7
8
|
from pydantic import validate_call
|
|
8
9
|
from typing_extensions import Self
|
|
10
|
+
from typing_extensions import deprecated
|
|
9
11
|
|
|
10
12
|
from shepherd_core.data_models.base.content import IdInt
|
|
11
13
|
from shepherd_core.data_models.base.content import NameStr
|
|
@@ -22,11 +24,9 @@ class TestbedTasks(ShpModel):
|
|
|
22
24
|
name: NameStr
|
|
23
25
|
observer_tasks: Annotated[list[ObserverTasks], Field(min_length=1, max_length=128)]
|
|
24
26
|
|
|
25
|
-
#
|
|
26
|
-
email_results: bool = False
|
|
27
|
-
owner_id: Optional[IdInt]
|
|
28
|
-
# TODO: had real email previously, does it really need these at all?
|
|
29
|
-
# DB stores experiment and knows when to email
|
|
27
|
+
# deprecated, TODO: remove before public release
|
|
28
|
+
email_results: Annotated[Optional[bool], deprecated("not needed anymore")] = False
|
|
29
|
+
owner_id: Annotated[Optional[IdInt], deprecated("not needed anymore")] = None
|
|
30
30
|
|
|
31
31
|
@classmethod
|
|
32
32
|
@validate_call
|
|
@@ -40,8 +40,6 @@ class TestbedTasks(ShpModel):
|
|
|
40
40
|
return cls(
|
|
41
41
|
name=xp.name,
|
|
42
42
|
observer_tasks=obs_tasks,
|
|
43
|
-
email_results=xp.email_results,
|
|
44
|
-
owner_id=xp.owner_id,
|
|
45
43
|
)
|
|
46
44
|
|
|
47
45
|
def get_observer_tasks(self, observer: str) -> Optional[ObserverTasks]:
|
|
@@ -50,11 +48,11 @@ class TestbedTasks(ShpModel):
|
|
|
50
48
|
return tasks
|
|
51
49
|
return None
|
|
52
50
|
|
|
53
|
-
def get_output_paths(self) -> dict:
|
|
51
|
+
def get_output_paths(self) -> dict[str, Path]:
|
|
54
52
|
# TODO: computed field preferred, but they don't work here, as
|
|
55
53
|
# - they are always stored in yaml despite "repr=False"
|
|
56
54
|
# - solution will shift to some kind of "result"-datatype that is combinable
|
|
57
|
-
values = {}
|
|
55
|
+
values: dict[str, Path] = {}
|
|
58
56
|
for obt in self.observer_tasks:
|
|
59
57
|
values = {**values, **obt.get_output_paths()}
|
|
60
58
|
return values
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
|
|
3
3
|
# more human-readable test-protocol @
|
|
4
|
-
# https://github.com/orgua/
|
|
4
|
+
# https://github.com/orgua/shepherd-v2-planning/blob/main/doc_testbed/Cape_pre-deployment-tests.xlsx
|
|
5
5
|
- datatype: cape
|
|
6
6
|
parameters:
|
|
7
7
|
id: 1270051
|
|
@@ -92,3 +92,11 @@
|
|
|
92
92
|
id: 1270065
|
|
93
93
|
name: cape65
|
|
94
94
|
comment: only 220u/440uF on 5V
|
|
95
|
+
- datatype: cape
|
|
96
|
+
parameters:
|
|
97
|
+
id: 42000
|
|
98
|
+
name: unit_testing_cape
|
|
99
|
+
version: none
|
|
100
|
+
description: for unit testing
|
|
101
|
+
created: 2025-04-29
|
|
102
|
+
calibrated: 2025-04-29
|
|
@@ -65,3 +65,10 @@ class GPIO(ShpModel, title="GPIO of Observer Node"):
|
|
|
65
65
|
|
|
66
66
|
def user_controllable(self) -> bool:
|
|
67
67
|
return ("gpio" in self.name.lower()) and (self.direction in {"IO", "OUT"})
|
|
68
|
+
|
|
69
|
+
def user_recordable(self) -> bool:
|
|
70
|
+
return (
|
|
71
|
+
("gpio" in self.name.lower())
|
|
72
|
+
and (self.direction in {"IO", "IN"})
|
|
73
|
+
and (self.pin_pru is not None)
|
|
74
|
+
)
|
|
@@ -43,7 +43,7 @@ class Observer(ShpModel, title="Shepherd-Sheep"):
|
|
|
43
43
|
|
|
44
44
|
latitude: Annotated[float, Field(ge=-90, le=90)] = 51.026573
|
|
45
45
|
longitude: Annotated[float, Field(ge=-180, le=180)] = 13.723291
|
|
46
|
-
|
|
46
|
+
""" ⤷ cfaed-floor"""
|
|
47
47
|
|
|
48
48
|
active: bool = True
|
|
49
49
|
cape: Optional[Cape] = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
# observer-cape-target-relation: https://github.com/orgua/
|
|
3
|
-
# network data: https://github.com/orgua/
|
|
2
|
+
# observer-cape-target-relation: https://github.com/orgua/shepherd-v2-planning/blob/main/doc_testbed/Cape_pre-deployment-tests.xlsx
|
|
3
|
+
# network data: https://github.com/orgua/shepherd-v2-planning/blob/main/doc_testbed/ethernet_MAC_addresses_bbones.ods
|
|
4
4
|
- datatype: observer
|
|
5
5
|
parameters:
|
|
6
6
|
id: 0
|
|
@@ -221,3 +221,20 @@
|
|
|
221
221
|
target_a:
|
|
222
222
|
name: nRF52_FRAM_1392_390
|
|
223
223
|
created: 2023-09-22 12:12:12
|
|
224
|
+
- datatype: observer
|
|
225
|
+
parameters:
|
|
226
|
+
id: 42
|
|
227
|
+
name: unit_testing_sheep
|
|
228
|
+
ip: 0.0.0.0
|
|
229
|
+
mac: 00:00:00:00:00:00
|
|
230
|
+
room: none
|
|
231
|
+
eth_port: none
|
|
232
|
+
description: unit testing
|
|
233
|
+
longitude: 0
|
|
234
|
+
latitude: 0
|
|
235
|
+
cape:
|
|
236
|
+
name: unit_testing_cape
|
|
237
|
+
target_a:
|
|
238
|
+
name: unit_testing_target
|
|
239
|
+
created: 2025-04-29 13:07:00
|
|
240
|
+
active: true
|
|
@@ -36,7 +36,7 @@ class Target(ShpModel, title="Target Node (DuT)"):
|
|
|
36
36
|
created: datetime = Field(default_factory=datetime.now)
|
|
37
37
|
|
|
38
38
|
testbed_id: Optional[IdInt16] = None
|
|
39
|
-
|
|
39
|
+
""" ⤷ is derived from ID (targets are still selected by id!)"""
|
|
40
40
|
mcu1: Union[MCU, NameStr]
|
|
41
41
|
mcu2: Union[MCU, NameStr, None] = None
|
|
42
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
# more human-readable test-protocol @
|
|
3
|
-
# https://github.com/orgua/
|
|
3
|
+
# https://github.com/orgua/shepherd-v2-planning/blob/main/doc_testbed/Target_pre-deployment-tests.xlsx
|
|
4
4
|
- datatype: target
|
|
5
5
|
parameters:
|
|
6
6
|
id: 6 # Outer ID - selected by user for XP - can be rearranged
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
# more human-readable test-protocol @
|
|
3
|
-
# https://github.com/orgua/
|
|
3
|
+
# https://github.com/orgua/shepherd-v2-planning/blob/main/doc_testbed/Target_pre-deployment-tests.xlsx
|
|
4
4
|
- datatype: target
|
|
5
5
|
parameters:
|
|
6
6
|
id: 2 # Outer ID - selected by user for XP - can be rearranged
|
|
@@ -161,3 +161,16 @@
|
|
|
161
161
|
name: nRF52
|
|
162
162
|
mcu2: null
|
|
163
163
|
created: 2022-12-12 12:12:12
|
|
164
|
+
|
|
165
|
+
- datatype: target
|
|
166
|
+
parameters:
|
|
167
|
+
id: 42
|
|
168
|
+
name: unit_testing_target
|
|
169
|
+
version: v0
|
|
170
|
+
description: for unit testing
|
|
171
|
+
comment: no comment
|
|
172
|
+
created: 2025-04-29
|
|
173
|
+
mcu1:
|
|
174
|
+
name: nRF52
|
|
175
|
+
mcu2:
|
|
176
|
+
name: MSP430FR
|
|
@@ -11,15 +11,17 @@ from pydantic import HttpUrl
|
|
|
11
11
|
from pydantic import model_validator
|
|
12
12
|
from typing_extensions import Self
|
|
13
13
|
|
|
14
|
+
from shepherd_core.config import config
|
|
14
15
|
from shepherd_core.data_models.base.content import IdInt
|
|
15
16
|
from shepherd_core.data_models.base.content import NameStr
|
|
16
17
|
from shepherd_core.data_models.base.content import SafeStr
|
|
17
18
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
18
|
-
from shepherd_core.logger import logger
|
|
19
19
|
from shepherd_core.testbed_client import tb_client
|
|
20
20
|
|
|
21
21
|
from .observer import Observer
|
|
22
22
|
|
|
23
|
+
duration_5min = timedelta(minutes=5)
|
|
24
|
+
|
|
23
25
|
|
|
24
26
|
class Testbed(ShpModel):
|
|
25
27
|
"""meta-data representation of a testbed-component (physical object)."""
|
|
@@ -36,21 +38,18 @@ class Testbed(ShpModel):
|
|
|
36
38
|
shared_storage: bool = True
|
|
37
39
|
data_on_server: Path
|
|
38
40
|
data_on_observer: Path
|
|
39
|
-
|
|
41
|
+
""" ⤷ storage layout: root_path/content_type/group/owner/[object]"""
|
|
42
|
+
# TODO: we might need individual paths for experiments & content
|
|
40
43
|
|
|
41
|
-
prep_duration: timedelta =
|
|
44
|
+
prep_duration: timedelta = duration_5min
|
|
42
45
|
# TODO: one BBone is currently time-keeper
|
|
43
46
|
|
|
44
47
|
@model_validator(mode="before")
|
|
45
48
|
@classmethod
|
|
46
49
|
def query_database(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
47
|
-
# allow instantiating an empty Testbed
|
|
48
|
-
# -> query the first (and only) entry of client
|
|
50
|
+
# allow instantiating an empty Testbed, take default in config
|
|
49
51
|
if len(values) == 0:
|
|
50
|
-
|
|
51
|
-
if len(ids) > 1:
|
|
52
|
-
logger.warning("More than one testbed defined?!?")
|
|
53
|
-
values = {"id": ids[0]}
|
|
52
|
+
values = {"name": config.TESTBED}
|
|
54
53
|
|
|
55
54
|
values, _ = tb_client.try_completing_model(cls.__name__, values)
|
|
56
55
|
return values
|
|
@@ -23,3 +23,14 @@
|
|
|
23
23
|
- name: sheep12
|
|
24
24
|
- name: sheep13
|
|
25
25
|
- name: sheep14
|
|
26
|
+
|
|
27
|
+
- datatype: testbed
|
|
28
|
+
parameters:
|
|
29
|
+
id: 42
|
|
30
|
+
name: unit_testing_testbed
|
|
31
|
+
description: "Artificial Testbed used in unit testing"
|
|
32
|
+
shared_storage: true
|
|
33
|
+
data_on_server: /tmp/
|
|
34
|
+
data_on_observer: /tmp/
|
|
35
|
+
observers:
|
|
36
|
+
- name: unit_testing_sheep
|
|
@@ -7,8 +7,7 @@ from typing import Optional
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
from pydantic import validate_call
|
|
9
9
|
|
|
10
|
-
from shepherd_core.
|
|
11
|
-
from shepherd_core.commons import UID_SIZE
|
|
10
|
+
from shepherd_core.config import config
|
|
12
11
|
from shepherd_core.logger import logger
|
|
13
12
|
|
|
14
13
|
from .validation import is_elf
|
|
@@ -50,7 +49,7 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
|
|
|
50
49
|
|
|
51
50
|
|
|
52
51
|
@validate_call
|
|
53
|
-
def read_symbol(file_elf: Path, symbol: str, length: int
|
|
52
|
+
def read_symbol(file_elf: Path, symbol: str, length: int) -> Optional[int]:
|
|
54
53
|
"""Read value of symbol in ELF-File.
|
|
55
54
|
|
|
56
55
|
Will be interpreted as int.
|
|
@@ -68,7 +67,7 @@ def read_symbol(file_elf: Path, symbol: str, length: int = UID_SIZE) -> Optional
|
|
|
68
67
|
|
|
69
68
|
def read_uid(file_elf: Path) -> Optional[int]:
|
|
70
69
|
"""Read value of UID-symbol for shepherd testbed."""
|
|
71
|
-
return read_symbol(file_elf, symbol=UID_NAME, length=UID_SIZE)
|
|
70
|
+
return read_symbol(file_elf, symbol=config.UID_NAME, length=config.UID_SIZE)
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
def read_arch(file_elf: Path) -> Optional[str]:
|
|
@@ -88,7 +87,7 @@ def read_arch(file_elf: Path) -> Optional[str]:
|
|
|
88
87
|
def modify_symbol_value(
|
|
89
88
|
file_elf: Path,
|
|
90
89
|
symbol: str,
|
|
91
|
-
value: Annotated[int, Field(ge=0, lt=2 ** (8 * UID_SIZE))],
|
|
90
|
+
value: Annotated[int, Field(ge=0, lt=2 ** (8 * config.UID_SIZE))],
|
|
92
91
|
*,
|
|
93
92
|
overwrite: bool = False,
|
|
94
93
|
) -> Optional[Path]:
|
|
@@ -106,10 +105,10 @@ def modify_symbol_value(
|
|
|
106
105
|
raise RuntimeError(elf_error_text)
|
|
107
106
|
elf = ELF(path=file_elf)
|
|
108
107
|
addr = elf.symbols[symbol]
|
|
109
|
-
value_raw = elf.read(address=addr, count=UID_SIZE)[-UID_SIZE:]
|
|
108
|
+
value_raw = elf.read(address=addr, count=config.UID_SIZE)[-config.UID_SIZE :]
|
|
110
109
|
# ⤷ cutting needed -> msp produces 4b instead of 2
|
|
111
110
|
value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
|
|
112
|
-
value_raw = value.to_bytes(length=UID_SIZE, byteorder=elf.endian, signed=False)
|
|
111
|
+
value_raw = value.to_bytes(length=config.UID_SIZE, byteorder=elf.endian, signed=False)
|
|
113
112
|
|
|
114
113
|
try:
|
|
115
114
|
elf.write(address=addr, data=value_raw)
|
|
@@ -132,4 +131,4 @@ def modify_symbol_value(
|
|
|
132
131
|
|
|
133
132
|
def modify_uid(file_elf: Path, value: int) -> Optional[Path]:
|
|
134
133
|
"""Replace value of UID-symbol for shepherd testbed."""
|
|
135
|
-
return modify_symbol_value(file_elf, symbol=UID_NAME, value=value, overwrite=True)
|
|
134
|
+
return modify_symbol_value(file_elf, symbol=config.UID_NAME, value=value, overwrite=True)
|
|
@@ -31,10 +31,8 @@ class SystemInventory(ShpModel):
|
|
|
31
31
|
"""System / OS related inventory model."""
|
|
32
32
|
|
|
33
33
|
uptime: PositiveInt
|
|
34
|
-
|
|
34
|
+
""" ⤷ seconds"""
|
|
35
35
|
timestamp: datetime
|
|
36
|
-
# time_delta: timedelta = timedelta(seconds=0) # noqa: ERA001
|
|
37
|
-
# ⤷ lag behind earliest observer, TODO: wrong place
|
|
38
36
|
|
|
39
37
|
system: str
|
|
40
38
|
release: str
|
shepherd_core/reader.py
CHANGED
|
@@ -7,6 +7,7 @@ import errno
|
|
|
7
7
|
import logging
|
|
8
8
|
import math
|
|
9
9
|
import os
|
|
10
|
+
from datetime import datetime
|
|
10
11
|
from itertools import product
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from types import MappingProxyType
|
|
@@ -24,9 +25,10 @@ from tqdm import trange
|
|
|
24
25
|
from typing_extensions import Self
|
|
25
26
|
from typing_extensions import deprecated
|
|
26
27
|
|
|
27
|
-
from .
|
|
28
|
+
from .config import config
|
|
28
29
|
from .data_models.base.calibration import CalibrationPair
|
|
29
30
|
from .data_models.base.calibration import CalibrationSeries
|
|
31
|
+
from .data_models.base.timezone import local_tz
|
|
30
32
|
from .data_models.content.energy_environment import EnergyDType
|
|
31
33
|
from .decoder_waveform import Uart
|
|
32
34
|
|
|
@@ -75,7 +77,7 @@ class Reader:
|
|
|
75
77
|
self._logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
|
76
78
|
|
|
77
79
|
if not hasattr(self, "samplerate_sps"):
|
|
78
|
-
self.samplerate_sps: int =
|
|
80
|
+
self.samplerate_sps: int = config.SAMPLERATE_SPS
|
|
79
81
|
self.sample_interval_ns: int = round(10**9 // self.samplerate_sps)
|
|
80
82
|
self.sample_interval_s: float = 1 / self.samplerate_sps
|
|
81
83
|
|
|
@@ -263,6 +265,11 @@ class Reader:
|
|
|
263
265
|
omit_timestamps=omit_ts,
|
|
264
266
|
)
|
|
265
267
|
|
|
268
|
+
def get_time_start(self) -> Optional[datetime]:
|
|
269
|
+
if self.samples_n < 1:
|
|
270
|
+
return None
|
|
271
|
+
return datetime.fromtimestamp(self._cal.time.raw_to_si(self.ds_time[0]), tz=local_tz())
|
|
272
|
+
|
|
266
273
|
def get_calibration_data(self) -> CalibrationSeries:
|
|
267
274
|
"""Read calibration-data from hdf5 file.
|
|
268
275
|
|
|
@@ -412,19 +419,20 @@ class Reader:
|
|
|
412
419
|
self.file_path.name,
|
|
413
420
|
)
|
|
414
421
|
# same length of datasets:
|
|
415
|
-
|
|
422
|
+
samples_n = self.h5file["data"]["time"].shape[0]
|
|
423
|
+
for dset in ["voltage", "current"]:
|
|
416
424
|
ds_size = self.h5file["data"][dset].shape[0]
|
|
417
|
-
if ds_size !=
|
|
425
|
+
if ds_size != samples_n:
|
|
418
426
|
self._logger.warning(
|
|
419
427
|
"[FileValidation] dataset '%s' has different size (=%d), "
|
|
420
|
-
"compared to
|
|
428
|
+
"compared to time (=%d), in '%s'",
|
|
421
429
|
dset,
|
|
422
430
|
ds_size,
|
|
423
|
-
|
|
431
|
+
samples_n,
|
|
424
432
|
self.file_path.name,
|
|
425
433
|
)
|
|
426
434
|
# dataset-length should be multiple of chunk-size
|
|
427
|
-
remaining_size =
|
|
435
|
+
remaining_size = samples_n % self.CHUNK_SAMPLES_N
|
|
428
436
|
if remaining_size != 0:
|
|
429
437
|
self._logger.warning(
|
|
430
438
|
"[FileValidation] datasets are not aligned with chunk-size in '%s'",
|
|
@@ -14,4 +14,4 @@ def _get_xdg_path(variable_name: str, default_path: Path) -> Path:
|
|
|
14
14
|
user_path = Path("~").expanduser()
|
|
15
15
|
|
|
16
16
|
cache_xdg_path = _get_xdg_path("XDG_CACHE_HOME", user_path / ".cache")
|
|
17
|
-
cache_user_path = cache_xdg_path / "
|
|
17
|
+
cache_user_path = cache_xdg_path / "shepherd"
|
|
@@ -8,7 +8,7 @@ from typing import Union
|
|
|
8
8
|
|
|
9
9
|
from pydantic import validate_call
|
|
10
10
|
|
|
11
|
-
from shepherd_core.
|
|
11
|
+
from shepherd_core.config import config
|
|
12
12
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
13
13
|
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
14
14
|
|
|
@@ -38,7 +38,7 @@ class WebClient(AbcClient):
|
|
|
38
38
|
if not hasattr(self, "_token"):
|
|
39
39
|
# add default values
|
|
40
40
|
self._token: str = "basic_public_access" # noqa: S105
|
|
41
|
-
self._server: str =
|
|
41
|
+
self._server: str = config.TESTBED_SERVER
|
|
42
42
|
self._user: Optional[User] = None
|
|
43
43
|
self._key: Optional[str] = None
|
|
44
44
|
self._connected: bool = False
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import pickle
|
|
5
|
+
from collections.abc import Iterable
|
|
5
6
|
from collections.abc import Mapping
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from datetime import timedelta
|
|
@@ -34,11 +35,11 @@ class Fixture:
|
|
|
34
35
|
|
|
35
36
|
def __init__(self, model_type: str) -> None:
|
|
36
37
|
self.model_type: str = model_type.lower()
|
|
37
|
-
self.elements_by_name: dict[str, dict] = {}
|
|
38
|
-
self.elements_by_id: dict[int, dict] = {}
|
|
38
|
+
self.elements_by_name: dict[str, dict[str, Any]] = {}
|
|
39
|
+
self.elements_by_id: dict[int, dict[str, Any]] = {}
|
|
39
40
|
# Iterator reset
|
|
40
41
|
self._iter_index: int = 0
|
|
41
|
-
self._iter_list: list = list(self.elements_by_name.values())
|
|
42
|
+
self._iter_list: list[dict[str, Any]] = list(self.elements_by_name.values())
|
|
42
43
|
|
|
43
44
|
def insert(self, data: Wrapper) -> None:
|
|
44
45
|
# ⤷ TODO: could get easier
|
|
@@ -54,9 +55,10 @@ class Fixture:
|
|
|
54
55
|
self.elements_by_name[name] = data_model
|
|
55
56
|
self.elements_by_id[_id] = data_model
|
|
56
57
|
# update iterator
|
|
57
|
-
self._iter_list = list(self.elements_by_name.values())
|
|
58
|
+
self._iter_list: list[dict[str, Any]] = list(self.elements_by_name.values())
|
|
58
59
|
|
|
59
|
-
def __getitem__(self, key: Union[str, int]) -> dict:
|
|
60
|
+
def __getitem__(self, key: Union[str, int]) -> dict[str, Any]:
|
|
61
|
+
original_key = key
|
|
60
62
|
if isinstance(key, str):
|
|
61
63
|
key = key.lower()
|
|
62
64
|
if key in self.elements_by_name:
|
|
@@ -65,7 +67,7 @@ class Fixture:
|
|
|
65
67
|
key = int(key)
|
|
66
68
|
if key in self.elements_by_id:
|
|
67
69
|
return self.elements_by_id[int(key)]
|
|
68
|
-
msg = f"{self.model_type} '{
|
|
70
|
+
msg = f"{self.model_type} '{original_key}' not found!"
|
|
69
71
|
raise ValueError(msg)
|
|
70
72
|
|
|
71
73
|
def __iter__(self) -> Self:
|
|
@@ -73,14 +75,14 @@ class Fixture:
|
|
|
73
75
|
self._iter_list = list(self.elements_by_name.values())
|
|
74
76
|
return self
|
|
75
77
|
|
|
76
|
-
def __next__(self) -> Any:
|
|
78
|
+
def __next__(self) -> dict[str, Any]:
|
|
77
79
|
if self._iter_index < len(self._iter_list):
|
|
78
80
|
member = self._iter_list[self._iter_index]
|
|
79
81
|
self._iter_index += 1
|
|
80
82
|
return member
|
|
81
83
|
raise StopIteration
|
|
82
84
|
|
|
83
|
-
def keys(self)
|
|
85
|
+
def keys(self) -> Iterable[str]:
|
|
84
86
|
return self.elements_by_name.keys()
|
|
85
87
|
|
|
86
88
|
def refs(self) -> dict:
|
|
@@ -149,13 +151,13 @@ class Fixture:
|
|
|
149
151
|
base[key] = value
|
|
150
152
|
return base
|
|
151
153
|
|
|
152
|
-
def query_id(self, _id: int) -> dict:
|
|
154
|
+
def query_id(self, _id: int) -> dict[str, Any]:
|
|
153
155
|
if isinstance(_id, int) and _id in self.elements_by_id:
|
|
154
156
|
return self.elements_by_id[_id]
|
|
155
157
|
msg = f"Initialization of {self.model_type} by ID failed - {_id} is unknown!"
|
|
156
158
|
raise ValueError(msg)
|
|
157
159
|
|
|
158
|
-
def query_name(self, name: str) -> dict:
|
|
160
|
+
def query_name(self, name: str) -> dict[str, Any]:
|
|
159
161
|
if isinstance(name, str) and name.lower() in self.elements_by_name:
|
|
160
162
|
return self.elements_by_name[name.lower()]
|
|
161
163
|
msg = f"Initialization of {self.model_type} by name failed - {name} is unknown!"
|
|
@@ -239,7 +241,7 @@ class Fixtures:
|
|
|
239
241
|
msg = f"Component '{key}' not found!"
|
|
240
242
|
raise ValueError(msg)
|
|
241
243
|
|
|
242
|
-
def keys(self)
|
|
244
|
+
def keys(self) -> Iterable[str]:
|
|
243
245
|
return self.components.keys()
|
|
244
246
|
|
|
245
247
|
@staticmethod
|
|
@@ -5,10 +5,7 @@ from hashlib import pbkdf2_hmac
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
from typing import Any
|
|
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 EmailStr
|
|
13
10
|
from pydantic import Field
|
|
14
11
|
from pydantic import SecretBytes
|
|
@@ -19,6 +16,7 @@ from pydantic import validate_call
|
|
|
19
16
|
|
|
20
17
|
from shepherd_core.data_models.base.content import NameStr
|
|
21
18
|
from shepherd_core.data_models.base.content import SafeStr
|
|
19
|
+
from shepherd_core.data_models.base.content import id_default
|
|
22
20
|
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
23
21
|
|
|
24
22
|
|
|
@@ -41,10 +39,9 @@ def hash_password(pw: Annotated[str, StringConstraints(min_length=20, max_length
|
|
|
41
39
|
class User(ShpModel):
|
|
42
40
|
"""meta-data representation of a testbed-component (physical object)."""
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
id: Union[UUID4, int] = Field(
|
|
42
|
+
id: int = Field(
|
|
46
43
|
description="Unique ID",
|
|
47
|
-
default_factory=
|
|
44
|
+
default_factory=id_default,
|
|
48
45
|
)
|
|
49
46
|
name: NameStr
|
|
50
47
|
description: Optional[SafeStr] = None
|
shepherd_core/version.py
CHANGED