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
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"""Collection of tasks for all observers included in experiment."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
4
6
|
from typing import Annotated
|
|
5
7
|
from typing import Optional
|
|
6
8
|
|
|
7
9
|
from pydantic import Field
|
|
8
10
|
from pydantic import validate_call
|
|
9
11
|
from typing_extensions import Self
|
|
12
|
+
from typing_extensions import deprecated
|
|
10
13
|
|
|
11
14
|
from shepherd_core.data_models.base.content import IdInt
|
|
12
15
|
from shepherd_core.data_models.base.content import NameStr
|
|
@@ -16,6 +19,9 @@ from shepherd_core.data_models.testbed.testbed import Testbed
|
|
|
16
19
|
|
|
17
20
|
from .observer_tasks import ObserverTasks
|
|
18
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Set as AbstractSet
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
class TestbedTasks(ShpModel):
|
|
21
27
|
"""Collection of tasks for all observers included in experiment."""
|
|
@@ -23,11 +29,9 @@ class TestbedTasks(ShpModel):
|
|
|
23
29
|
name: NameStr
|
|
24
30
|
observer_tasks: Annotated[list[ObserverTasks], Field(min_length=1, max_length=128)]
|
|
25
31
|
|
|
26
|
-
#
|
|
27
|
-
email_results: bool = False
|
|
28
|
-
owner_id: Optional[IdInt]
|
|
29
|
-
# TODO: had real email previously, does it really need these at all?
|
|
30
|
-
# DB stores experiment and knows when to email
|
|
32
|
+
# deprecated, TODO: remove before public release
|
|
33
|
+
email_results: Annotated[Optional[bool], deprecated("not needed anymore")] = False
|
|
34
|
+
owner_id: Annotated[Optional[IdInt], deprecated("not needed anymore")] = None
|
|
31
35
|
|
|
32
36
|
@classmethod
|
|
33
37
|
@validate_call
|
|
@@ -41,8 +45,6 @@ class TestbedTasks(ShpModel):
|
|
|
41
45
|
return cls(
|
|
42
46
|
name=xp.name,
|
|
43
47
|
observer_tasks=obs_tasks,
|
|
44
|
-
email_results=xp.email_results,
|
|
45
|
-
owner_id=xp.owner_id,
|
|
46
48
|
)
|
|
47
49
|
|
|
48
50
|
def get_observer_tasks(self, observer: str) -> Optional[ObserverTasks]:
|
|
@@ -59,3 +61,10 @@ class TestbedTasks(ShpModel):
|
|
|
59
61
|
for obt in self.observer_tasks:
|
|
60
62
|
values = {**values, **obt.get_output_paths()}
|
|
61
63
|
return values
|
|
64
|
+
|
|
65
|
+
def is_contained(self) -> bool:
|
|
66
|
+
paths_allowed: AbstractSet[PurePosixPath] = {
|
|
67
|
+
PurePosixPath("/var/shepherd/"),
|
|
68
|
+
PurePosixPath("/tmp/"), # noqa: S108
|
|
69
|
+
}
|
|
70
|
+
return all(obt.is_contained(paths_allowed) for obt in self.observer_tasks)
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -28,7 +28,7 @@ from typing import Union
|
|
|
28
28
|
|
|
29
29
|
import numpy as np
|
|
30
30
|
|
|
31
|
-
from shepherd_core.logger import
|
|
31
|
+
from shepherd_core.logger import log
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class Parity(str, Enum):
|
|
@@ -109,10 +109,10 @@ class Uart:
|
|
|
109
109
|
raise ValueError("Only bit-order LSB-first is supported ATM")
|
|
110
110
|
|
|
111
111
|
if self.inversion:
|
|
112
|
-
|
|
112
|
+
log.debug("inversion was detected / issued -> will invert signal")
|
|
113
113
|
self._convert_analog2digital(invert=True)
|
|
114
114
|
if self.detect_inversion():
|
|
115
|
-
|
|
115
|
+
log.error("Signal still inverted?!? Check parameters and input")
|
|
116
116
|
|
|
117
117
|
# results
|
|
118
118
|
self.events_symbols: Optional[np.ndarray] = None
|
|
@@ -136,7 +136,7 @@ class Uart:
|
|
|
136
136
|
self.events_sig = self.events_sig[data_f == 1]
|
|
137
137
|
|
|
138
138
|
if len(data_0) > len(self.events_sig):
|
|
139
|
-
|
|
139
|
+
log.debug(
|
|
140
140
|
"filtered out %d/%d events (redundant)",
|
|
141
141
|
len(data_0) - len(self.events_sig),
|
|
142
142
|
len(data_0),
|
|
@@ -145,7 +145,7 @@ class Uart:
|
|
|
145
145
|
def _add_duration(self) -> None:
|
|
146
146
|
"""Calculate third column -> duration of state in [baud-ticks]."""
|
|
147
147
|
if self.events_sig.shape[1] > 2:
|
|
148
|
-
|
|
148
|
+
log.warning("Tried to add state-duration, but it seems already present")
|
|
149
149
|
return
|
|
150
150
|
if not hasattr(self, "dur_tick"):
|
|
151
151
|
raise ValueError("Make sure that baud-rate was calculated before running add_dur()")
|
|
@@ -223,7 +223,7 @@ class Uart:
|
|
|
223
223
|
symbol = 0
|
|
224
224
|
pos_df = None
|
|
225
225
|
else:
|
|
226
|
-
|
|
226
|
+
log.debug("Error - Long pause - but SigLow (@%d)", time)
|
|
227
227
|
continue
|
|
228
228
|
if pos_df is None and value == 0:
|
|
229
229
|
# Start of frame (first low after pause / EOF)
|
|
@@ -245,7 +245,7 @@ class Uart:
|
|
|
245
245
|
symbol = 0
|
|
246
246
|
pos_df = None
|
|
247
247
|
if off_tick and value == 0:
|
|
248
|
-
|
|
248
|
+
log.debug("Error - Off-sized step - but SigLow (@%d)", time)
|
|
249
249
|
self.events_symbols = np.concatenate(content).reshape((len(content), 2))
|
|
250
250
|
# TODO: numpy is converting timestamp to string -> must be added as tuple (ts, symbol)
|
|
251
251
|
# symbol_events[:, 0] = symbol_events[:, 0].astype(float) # noqa: ERA001
|
|
@@ -7,9 +7,8 @@ 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.
|
|
12
|
-
from shepherd_core.logger import logger
|
|
10
|
+
from shepherd_core.config import config
|
|
11
|
+
from shepherd_core.logger import log
|
|
13
12
|
|
|
14
13
|
from .validation import is_elf
|
|
15
14
|
|
|
@@ -36,9 +35,9 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
|
|
|
36
35
|
except KeyError:
|
|
37
36
|
addr = None
|
|
38
37
|
if addr is None:
|
|
39
|
-
|
|
38
|
+
log.debug("Symbol '%s' not found in ELF-File %s", symbol, file_elf.name)
|
|
40
39
|
return False
|
|
41
|
-
|
|
40
|
+
log.debug(
|
|
42
41
|
"Symbol '%s' found in ELF-File %s, arch=%s, order=%s",
|
|
43
42
|
symbol,
|
|
44
43
|
file_elf.name,
|
|
@@ -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]:
|
|
@@ -80,7 +79,7 @@ def read_arch(file_elf: Path) -> Optional[str]:
|
|
|
80
79
|
elf = ELF(path=file_elf)
|
|
81
80
|
if "exec" in elf.elftype.lower():
|
|
82
81
|
return elf.arch.lower()
|
|
83
|
-
|
|
82
|
+
log.error("ELF is not Executable")
|
|
84
83
|
return None
|
|
85
84
|
|
|
86
85
|
|
|
@@ -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,21 +105,21 @@ 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)
|
|
116
115
|
except AttributeError:
|
|
117
|
-
|
|
116
|
+
log.warning("ELF-Modifier failed @%s for symbol '%s'", f"0x{addr:X}", symbol)
|
|
118
117
|
return None
|
|
119
118
|
|
|
120
119
|
file_new = file_elf if overwrite else file_elf.with_stem(file_elf.stem + "_" + str(value))
|
|
121
120
|
elf.save(path=file_new)
|
|
122
121
|
elf.close()
|
|
123
|
-
|
|
122
|
+
log.debug(
|
|
124
123
|
"Value of Symbol '%s' modified: %s -> %s @%s",
|
|
125
124
|
symbol,
|
|
126
125
|
hex(value_old),
|
|
@@ -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)
|
|
@@ -13,7 +13,7 @@ from intelhex import IntelHexError
|
|
|
13
13
|
from pydantic import validate_call
|
|
14
14
|
|
|
15
15
|
from shepherd_core.data_models.content.firmware_datatype import FirmwareDType
|
|
16
|
-
from shepherd_core.logger import
|
|
16
|
+
from shepherd_core.logger import log
|
|
17
17
|
|
|
18
18
|
from .converter_elf import elf_to_hex
|
|
19
19
|
|
|
@@ -94,7 +94,7 @@ def is_elf(file: Path) -> bool:
|
|
|
94
94
|
try:
|
|
95
95
|
_ = ELF(path=file)
|
|
96
96
|
except ELFError:
|
|
97
|
-
|
|
97
|
+
log.debug("File %s is not ELF - Magic number does not match", file.name)
|
|
98
98
|
return False
|
|
99
99
|
return True
|
|
100
100
|
|
|
@@ -14,7 +14,7 @@ from typing import Optional
|
|
|
14
14
|
from typing_extensions import Self
|
|
15
15
|
|
|
16
16
|
from shepherd_core.data_models.base.timezone import local_now
|
|
17
|
-
from shepherd_core.logger import
|
|
17
|
+
from shepherd_core.logger import log
|
|
18
18
|
|
|
19
19
|
try:
|
|
20
20
|
import psutil
|
|
@@ -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
|
|
@@ -64,7 +62,7 @@ class SystemInventory(ShpModel):
|
|
|
64
62
|
if psutil is None:
|
|
65
63
|
ifs2 = {}
|
|
66
64
|
uptime = 0
|
|
67
|
-
|
|
65
|
+
log.warning(
|
|
68
66
|
"Inventory-Parameters will be missing. "
|
|
69
67
|
"Please install functionality with "
|
|
70
68
|
"'pip install shepherd_core[inventory] -U' first"
|
shepherd_core/logger.py
CHANGED
|
@@ -7,8 +7,8 @@ from typing import Union
|
|
|
7
7
|
import chromalog
|
|
8
8
|
|
|
9
9
|
chromalog.basicConfig(format="%(message)s")
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
log = logging.getLogger("SHPCore")
|
|
11
|
+
log.addHandler(logging.NullHandler())
|
|
12
12
|
|
|
13
13
|
verbose_level: int = 2
|
|
14
14
|
|
|
@@ -47,7 +47,7 @@ def increase_verbose_level(verbose: int) -> None:
|
|
|
47
47
|
global verbose_level # noqa: PLW0603
|
|
48
48
|
if verbose >= verbose_level:
|
|
49
49
|
verbose_level = min(max(verbose, 0), 3)
|
|
50
|
-
set_log_verbose_level(
|
|
50
|
+
set_log_verbose_level(log, verbose_level)
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
increase_verbose_level(2)
|
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
|
|
|
@@ -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
|
|
@@ -18,7 +18,7 @@ from typing_extensions import Self
|
|
|
18
18
|
from shepherd_core.data_models.base.timezone import local_now
|
|
19
19
|
from shepherd_core.data_models.base.timezone import local_tz
|
|
20
20
|
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
21
|
-
from shepherd_core.logger import
|
|
21
|
+
from shepherd_core.logger import log
|
|
22
22
|
|
|
23
23
|
from .cache_path import cache_user_path
|
|
24
24
|
|
|
@@ -109,7 +109,7 @@ class Fixture:
|
|
|
109
109
|
raise ValueError(msg)
|
|
110
110
|
chain.append(base_name)
|
|
111
111
|
fixture_base = copy.copy(self[fixture_name])
|
|
112
|
-
|
|
112
|
+
log.debug("'%s' will inherit from '%s'", self.model_type, fixture_name)
|
|
113
113
|
fixture_base["name"] = fixture_name
|
|
114
114
|
chain.append(fixture_name)
|
|
115
115
|
base_dict, chain = self.inheritance(values=fixture_base, chain=chain)
|
|
@@ -196,7 +196,7 @@ class Fixtures:
|
|
|
196
196
|
# TODO: also add version as criterion
|
|
197
197
|
with cache_file.open("rb", buffering=-1) as fd:
|
|
198
198
|
self.components = pickle.load(fd) # noqa: S301
|
|
199
|
-
|
|
199
|
+
log.debug(" -> found & used pickled fixtures")
|
|
200
200
|
else:
|
|
201
201
|
if self.file_path.is_file():
|
|
202
202
|
files = [self.file_path]
|
|
@@ -204,7 +204,7 @@ class Fixtures:
|
|
|
204
204
|
files = list(
|
|
205
205
|
self.file_path.glob("**/*" + self.suffix)
|
|
206
206
|
) # for py>=3.12: case_sensitive=False
|
|
207
|
-
|
|
207
|
+
log.debug(" -> got %s %s-files", len(files), self.suffix)
|
|
208
208
|
else:
|
|
209
209
|
raise ValueError("Path must either be file or directory (or empty)")
|
|
210
210
|
|
|
@@ -212,7 +212,7 @@ class Fixtures:
|
|
|
212
212
|
self.insert_file(file)
|
|
213
213
|
|
|
214
214
|
if len(self.components) < 1:
|
|
215
|
-
|
|
215
|
+
log.error(f"No fixture-components found at {self.file_path.as_posix()}")
|
|
216
216
|
elif sheep_detect:
|
|
217
217
|
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
218
218
|
with cache_file.open("wb", buffering=-1) as fd:
|
shepherd_core/version.py
CHANGED
|
@@ -17,7 +17,7 @@ Compromises:
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from shepherd_core.data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
20
|
-
from shepherd_core.logger import
|
|
20
|
+
from shepherd_core.logger import log
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class VirtualHarvesterModel:
|
|
@@ -37,7 +37,7 @@ class VirtualHarvesterModel:
|
|
|
37
37
|
|
|
38
38
|
self.is_emu: bool = bool(self._cfg.hrv_mode & (2**0))
|
|
39
39
|
if not self.is_emu:
|
|
40
|
-
|
|
40
|
+
log.warning(
|
|
41
41
|
"This VSrc-config is not meant for emulation-mode -> activate 'is_emu' flag."
|
|
42
42
|
)
|
|
43
43
|
|
|
@@ -16,7 +16,7 @@ from tqdm import tqdm
|
|
|
16
16
|
|
|
17
17
|
from shepherd_core.data_models.base.calibration import CalibrationEmulator
|
|
18
18
|
from shepherd_core.data_models.content.virtual_source import VirtualSourceConfig
|
|
19
|
-
from shepherd_core.logger import
|
|
19
|
+
from shepherd_core.logger import log
|
|
20
20
|
from shepherd_core.reader import Reader
|
|
21
21
|
from shepherd_core.writer import Writer
|
|
22
22
|
|
|
@@ -68,7 +68,7 @@ def simulate_source(
|
|
|
68
68
|
# keep dependencies low
|
|
69
69
|
from matplotlib import pyplot as plt
|
|
70
70
|
except ImportError:
|
|
71
|
-
|
|
71
|
+
log.warning("Matplotlib not installed, plotting of internals disabled")
|
|
72
72
|
stats_internal = None
|
|
73
73
|
else:
|
|
74
74
|
stats_internal = None
|
shepherd_core/writer.py
CHANGED
|
@@ -20,7 +20,7 @@ from typing_extensions import Self
|
|
|
20
20
|
from yaml import Node
|
|
21
21
|
from yaml import SafeDumper
|
|
22
22
|
|
|
23
|
-
from .
|
|
23
|
+
from .config import config
|
|
24
24
|
from .data_models.base.calibration import CalibrationEmulator as CalEmu
|
|
25
25
|
from .data_models.base.calibration import CalibrationHarvester as CalHrv
|
|
26
26
|
from .data_models.base.calibration import CalibrationSeries as CalSeries
|
|
@@ -348,7 +348,7 @@ class Writer(Reader):
|
|
|
348
348
|
chunks_n = self.ds_voltage.size / self.CHUNK_SAMPLES_N
|
|
349
349
|
size_new = int(math.floor(chunks_n) * self.CHUNK_SAMPLES_N)
|
|
350
350
|
if size_new < self.ds_voltage.size:
|
|
351
|
-
if self.samplerate_sps !=
|
|
351
|
+
if self.samplerate_sps != config.SAMPLERATE_SPS:
|
|
352
352
|
self._logger.debug("skipped alignment due to altered samplerate")
|
|
353
353
|
return
|
|
354
354
|
self._logger.info(
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shepherd_core
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.6.2
|
|
4
4
|
Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
|
|
5
5
|
Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
7
|
-
Project-URL: Documentation, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
7
|
+
Project-URL: Documentation, https://github.com/nes-lab/shepherd-tools/blob/main/README.md
|
|
8
|
+
Project-URL: Issues, https://github.com/nes-lab/shepherd-tools/issues
|
|
9
9
|
Project-URL: Source, https://pypi.org/project/shepherd-core/
|
|
10
10
|
Keywords: testbed,beaglebone,pru,batteryless,energyharvesting,solar
|
|
11
11
|
Platform: unix
|
|
@@ -56,16 +56,16 @@ Requires-Dist: coverage; extra == "test"
|
|
|
56
56
|
|
|
57
57
|
# Core Library
|
|
58
58
|
|
|
59
|
-
[](https://pypi.org/project/shepherd_core)
|
|
60
60
|
[](https://pypi.python.org/pypi/shepherd-core)
|
|
61
|
-
[](https://github.com/nes-lab/shepherd-tools/actions/workflows/quality_assurance.yaml)
|
|
62
62
|
[](https://github.com/astral-sh/ruff)
|
|
63
63
|
|
|
64
|
-
**Main Documentation**: <https://
|
|
64
|
+
**Main Documentation**: <https://nes-lab.github.io/shepherd>
|
|
65
65
|
|
|
66
|
-
**Source Code**: <https://github.com/
|
|
66
|
+
**Source Code**: <https://github.com/nes-lab/shepherd-tools>
|
|
67
67
|
|
|
68
|
-
**Main Project**: <https://github.com/
|
|
68
|
+
**Main Project**: <https://github.com/nes-lab/shepherd>
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
@@ -87,7 +87,7 @@ For postprocessing shepherds .h5-files usage of [shepherd_data](https://pypi.org
|
|
|
87
87
|
- decode waveforms (gpio-state & timestamp) to UART
|
|
88
88
|
- create an inventory (for deployed versions of software, hardware)
|
|
89
89
|
|
|
90
|
-
See [official documentation](https://
|
|
90
|
+
See [official documentation](https://nes-lab.github.io/shepherd) or [example scripts](https://github.com/nes-lab/shepherd-tools/tree/main/shepherd_core/examples) for more details and usage. Most functionality is showcased in both. The [extra](https://github.com/nes-lab/shepherd-tools/tree/main/shepherd_core/extra)-directory holds data-generators relevant for the testbed. Notably is a [trafficbench](https://github.com/nes-lab/TrafficBench)-experiment that's used to derive the link-matrix of the testbed-nodes.
|
|
91
91
|
|
|
92
92
|
## Config-Models in Detail
|
|
93
93
|
|
|
@@ -145,9 +145,9 @@ pip install shepherd-data -U
|
|
|
145
145
|
For bleeding-edge-features or dev-work it is possible to install directly from GitHub-Sources (here `dev`-branch):
|
|
146
146
|
|
|
147
147
|
```Shell
|
|
148
|
-
pip install git+https://github.com/
|
|
148
|
+
pip install git+https://github.com/nes-lab/shepherd-tools.git@dev#subdirectory=shepherd_core -U
|
|
149
149
|
# and on sheep with newer debian
|
|
150
|
-
sudo pip install git+https://github.com/
|
|
150
|
+
sudo pip install git+https://github.com/nes-lab/shepherd-tools.git@dev#subdirectory=shepherd_core -U --break-system-packages
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
If you are working with ``.elf``-files (embedding into experiments) you make "objcopy" accessible to python. In Ubuntu, you can either install ``build-essential`` or ``binutils-$ARCH`` with arch being ``msp430`` or ``arm-none-eabi`` for the nRF52.
|
|
@@ -179,7 +179,7 @@ To run the testbench, follow these steps:
|
|
|
179
179
|
3. run the testbench (~ 320 tests):
|
|
180
180
|
|
|
181
181
|
```Shell
|
|
182
|
-
cd shepherd-
|
|
182
|
+
cd shepherd-tools/shepherd_core
|
|
183
183
|
pip3 install ./[tests]
|
|
184
184
|
pytest
|
|
185
185
|
```
|