shepherd-core 2025.4.1__py3-none-any.whl → 2025.5.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/calibration_hw_def.py +11 -11
- shepherd_core/commons.py +4 -4
- shepherd_core/data_models/__init__.py +2 -0
- shepherd_core/data_models/base/cal_measurement.py +10 -11
- shepherd_core/data_models/base/calibration.py +7 -6
- shepherd_core/data_models/base/content.py +1 -1
- shepherd_core/data_models/base/shepherd.py +6 -7
- shepherd_core/data_models/base/wrapper.py +2 -2
- shepherd_core/data_models/content/_external_fixtures.yaml +32 -32
- shepherd_core/data_models/content/energy_environment.py +6 -5
- shepherd_core/data_models/content/firmware.py +9 -7
- shepherd_core/data_models/content/virtual_harvester.py +34 -26
- shepherd_core/data_models/content/virtual_harvester_fixture.yaml +2 -2
- shepherd_core/data_models/content/virtual_source.py +20 -17
- shepherd_core/data_models/content/virtual_source_fixture.yaml +3 -3
- shepherd_core/data_models/experiment/experiment.py +15 -15
- shepherd_core/data_models/experiment/observer_features.py +109 -16
- shepherd_core/data_models/experiment/target_config.py +17 -12
- shepherd_core/data_models/task/__init__.py +11 -8
- shepherd_core/data_models/task/emulation.py +32 -17
- shepherd_core/data_models/task/firmware_mod.py +11 -11
- shepherd_core/data_models/task/harvest.py +7 -6
- shepherd_core/data_models/task/observer_tasks.py +7 -7
- shepherd_core/data_models/task/programming.py +13 -12
- shepherd_core/data_models/task/testbed_tasks.py +8 -8
- shepherd_core/data_models/testbed/cape.py +7 -6
- shepherd_core/data_models/testbed/gpio.py +8 -7
- shepherd_core/data_models/testbed/mcu.py +8 -7
- shepherd_core/data_models/testbed/mcu_fixture.yaml +4 -4
- shepherd_core/data_models/testbed/observer.py +9 -7
- shepherd_core/data_models/testbed/target.py +9 -7
- shepherd_core/data_models/testbed/testbed.py +11 -10
- shepherd_core/data_models/virtual_source_doc.txt +3 -3
- shepherd_core/decoder_waveform/uart.py +5 -5
- shepherd_core/fw_tools/converter.py +10 -6
- shepherd_core/fw_tools/patcher.py +14 -15
- shepherd_core/fw_tools/validation.py +11 -6
- shepherd_core/inventory/__init__.py +6 -6
- shepherd_core/inventory/python.py +1 -1
- shepherd_core/inventory/system.py +11 -8
- shepherd_core/inventory/target.py +3 -3
- shepherd_core/logger.py +2 -2
- shepherd_core/reader.py +105 -78
- shepherd_core/testbed_client/client_abc_fix.py +22 -16
- shepherd_core/testbed_client/client_web.py +18 -11
- shepherd_core/testbed_client/fixtures.py +21 -22
- shepherd_core/testbed_client/user_model.py +6 -5
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/target_model.py +3 -3
- shepherd_core/vsource/virtual_converter_model.py +3 -3
- shepherd_core/vsource/virtual_harvester_model.py +7 -9
- shepherd_core/vsource/virtual_harvester_simulation.py +7 -6
- shepherd_core/vsource/virtual_source_model.py +6 -5
- shepherd_core/vsource/virtual_source_simulation.py +8 -7
- shepherd_core/writer.py +37 -39
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/METADATA +2 -3
- shepherd_core-2025.5.2.dist-info/RECORD +81 -0
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/WHEEL +1 -1
- shepherd_core-2025.4.1.dist-info/RECORD +0 -81
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.5.2.dist-info}/zip-safe +0 -0
|
@@ -9,7 +9,8 @@ from typing import Union
|
|
|
9
9
|
import zstandard as zstd
|
|
10
10
|
from pydantic import validate_call
|
|
11
11
|
|
|
12
|
-
from
|
|
12
|
+
from shepherd_core.data_models.content.firmware_datatype import FirmwareDType
|
|
13
|
+
|
|
13
14
|
from .converter_elf import elf_to_hex
|
|
14
15
|
from .validation import is_elf
|
|
15
16
|
from .validation import is_hex
|
|
@@ -27,7 +28,8 @@ def firmware_to_hex(file_path: Path) -> Path:
|
|
|
27
28
|
return elf_to_hex(file_path)
|
|
28
29
|
if is_hex(file_path):
|
|
29
30
|
return file_path
|
|
30
|
-
|
|
31
|
+
msg = (f"FW2Hex: unknown file '{file_path.name}', it should be ELF or HEX",)
|
|
32
|
+
raise FileNotFoundError(msg)
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
@validate_call
|
|
@@ -85,10 +87,10 @@ def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path
|
|
|
85
87
|
- base64-string will be transformed to file
|
|
86
88
|
- if data is a path the file will be copied to the destination.
|
|
87
89
|
"""
|
|
88
|
-
if data_type == FirmwareDType.base64_elf:
|
|
90
|
+
if data_type == FirmwareDType.base64_elf and isinstance(data, str):
|
|
89
91
|
file = file_path.with_suffix(".elf")
|
|
90
92
|
base64_to_file(data, file)
|
|
91
|
-
elif data_type == FirmwareDType.base64_hex:
|
|
93
|
+
elif data_type == FirmwareDType.base64_hex and isinstance(data, str):
|
|
92
94
|
file = file_path.with_suffix(".hex")
|
|
93
95
|
base64_to_file(data, file)
|
|
94
96
|
elif isinstance(data, (Path, str)):
|
|
@@ -97,10 +99,12 @@ def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path
|
|
|
97
99
|
elif data_type == FirmwareDType.path_hex:
|
|
98
100
|
file = file_path.with_suffix(".hex")
|
|
99
101
|
else:
|
|
100
|
-
|
|
102
|
+
msg = "FW-Extraction failed due to unknown datatype '{data_type}'"
|
|
103
|
+
raise ValueError(msg)
|
|
101
104
|
if not file.parent.exists():
|
|
102
105
|
file.parent.mkdir(parents=True)
|
|
103
106
|
shutil.copy(data, file)
|
|
104
107
|
else:
|
|
105
|
-
|
|
108
|
+
msg = f"FW-Extraction failed due to unknown data-type '{type(data)}'"
|
|
109
|
+
raise ValueError(msg)
|
|
106
110
|
return file
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
"""Read and modify symbols in ELF-files."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
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
|
-
from typing_extensions import Annotated
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
10
|
+
from shepherd_core.commons import UID_NAME
|
|
11
|
+
from shepherd_core.commons import UID_SIZE
|
|
12
|
+
from shepherd_core.logger import logger
|
|
13
|
+
|
|
13
14
|
from .validation import is_elf
|
|
14
15
|
|
|
15
16
|
try:
|
|
@@ -49,7 +50,7 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
|
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
@validate_call
|
|
52
|
-
def read_symbol(file_elf: Path, symbol: str, length: int =
|
|
53
|
+
def read_symbol(file_elf: Path, symbol: str, length: int = UID_SIZE) -> Optional[int]:
|
|
53
54
|
"""Read value of symbol in ELF-File.
|
|
54
55
|
|
|
55
56
|
Will be interpreted as int.
|
|
@@ -67,7 +68,7 @@ def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> O
|
|
|
67
68
|
|
|
68
69
|
def read_uid(file_elf: Path) -> Optional[int]:
|
|
69
70
|
"""Read value of UID-symbol for shepherd testbed."""
|
|
70
|
-
return read_symbol(file_elf, symbol=
|
|
71
|
+
return read_symbol(file_elf, symbol=UID_NAME, length=UID_SIZE)
|
|
71
72
|
|
|
72
73
|
|
|
73
74
|
def read_arch(file_elf: Path) -> Optional[str]:
|
|
@@ -87,7 +88,7 @@ def read_arch(file_elf: Path) -> Optional[str]:
|
|
|
87
88
|
def modify_symbol_value(
|
|
88
89
|
file_elf: Path,
|
|
89
90
|
symbol: str,
|
|
90
|
-
value: Annotated[int, Field(ge=0, lt=2 ** (8 *
|
|
91
|
+
value: Annotated[int, Field(ge=0, lt=2 ** (8 * UID_SIZE))],
|
|
91
92
|
*,
|
|
92
93
|
overwrite: bool = False,
|
|
93
94
|
) -> Optional[Path]:
|
|
@@ -105,20 +106,18 @@ def modify_symbol_value(
|
|
|
105
106
|
raise RuntimeError(elf_error_text)
|
|
106
107
|
elf = ELF(path=file_elf)
|
|
107
108
|
addr = elf.symbols[symbol]
|
|
108
|
-
value_raw = elf.read(address=addr, count=
|
|
109
|
+
value_raw = elf.read(address=addr, count=UID_SIZE)[-UID_SIZE:]
|
|
109
110
|
# ⤷ cutting needed -> msp produces 4b instead of 2
|
|
110
111
|
value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
|
|
111
|
-
value_raw = value.to_bytes(length=
|
|
112
|
+
value_raw = value.to_bytes(length=UID_SIZE, byteorder=elf.endian, signed=False)
|
|
113
|
+
|
|
112
114
|
try:
|
|
113
115
|
elf.write(address=addr, data=value_raw)
|
|
114
116
|
except AttributeError:
|
|
115
117
|
logger.warning("ELF-Modifier failed @%s for symbol '%s'", f"0x{addr:X}", symbol)
|
|
116
118
|
return None
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
else:
|
|
120
|
-
file_new = file_elf.with_name(file_elf.stem + "_" + str(value) + file_elf.suffix)
|
|
121
|
-
# could be simplified, but py3.8-- doesn't know .with_stem()
|
|
119
|
+
|
|
120
|
+
file_new = file_elf if overwrite else file_elf.with_stem(file_elf.stem + "_" + str(value))
|
|
122
121
|
elf.save(path=file_new)
|
|
123
122
|
elf.close()
|
|
124
123
|
logger.debug(
|
|
@@ -133,4 +132,4 @@ def modify_symbol_value(
|
|
|
133
132
|
|
|
134
133
|
def modify_uid(file_elf: Path, value: int) -> Optional[Path]:
|
|
135
134
|
"""Replace value of UID-symbol for shepherd testbed."""
|
|
136
|
-
return modify_symbol_value(file_elf, symbol=
|
|
135
|
+
return modify_symbol_value(file_elf, symbol=UID_NAME, value=value, overwrite=True)
|
|
@@ -12,8 +12,9 @@ from intelhex import IntelHex
|
|
|
12
12
|
from intelhex import IntelHexError
|
|
13
13
|
from pydantic import validate_call
|
|
14
14
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
15
|
+
from shepherd_core.data_models.content.firmware_datatype import FirmwareDType
|
|
16
|
+
from shepherd_core.logger import logger
|
|
17
|
+
|
|
17
18
|
from .converter_elf import elf_to_hex
|
|
18
19
|
|
|
19
20
|
try:
|
|
@@ -136,7 +137,8 @@ def determine_type(file: Path) -> FirmwareDType:
|
|
|
136
137
|
return FirmwareDType.path_hex
|
|
137
138
|
if is_elf(file):
|
|
138
139
|
return FirmwareDType.path_elf
|
|
139
|
-
|
|
140
|
+
msg = f"Type of file '{file.name}' could not be determined"
|
|
141
|
+
raise ValueError(msg)
|
|
140
142
|
|
|
141
143
|
|
|
142
144
|
def determine_arch(file: Path) -> str:
|
|
@@ -147,11 +149,14 @@ def determine_arch(file: Path) -> str:
|
|
|
147
149
|
return "msp430"
|
|
148
150
|
if is_elf_nrf52(file):
|
|
149
151
|
return "nrf52"
|
|
150
|
-
|
|
152
|
+
msg = f"Arch of ELF '{file.name}' could not be determined"
|
|
153
|
+
raise ValueError(msg)
|
|
151
154
|
if file_t == FirmwareDType.path_hex:
|
|
152
155
|
if is_hex_msp430(file):
|
|
153
156
|
return "msp430"
|
|
154
157
|
if is_hex_nrf52(file):
|
|
155
158
|
return "nrf52"
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
msg = f"Arch of HEX '{file.name}' could not be determined"
|
|
160
|
+
raise ValueError(msg)
|
|
161
|
+
msg = f"Arch of file '{file.name}' could not be determined"
|
|
162
|
+
raise ValueError(msg)
|
|
@@ -9,13 +9,13 @@ This will collect:
|
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from datetime import timedelta
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Annotated
|
|
13
13
|
|
|
14
14
|
from pydantic import Field
|
|
15
|
-
from typing_extensions import Annotated
|
|
16
15
|
from typing_extensions import Self
|
|
17
16
|
|
|
18
|
-
from
|
|
17
|
+
from shepherd_core.data_models import ShpModel
|
|
18
|
+
|
|
19
19
|
from .python import PythonInventory
|
|
20
20
|
from .system import SystemInventory
|
|
21
21
|
from .target import TargetInventory
|
|
@@ -55,7 +55,7 @@ class Inventory(PythonInventory, SystemInventory, TargetInventory):
|
|
|
55
55
|
class InventoryList(ShpModel):
|
|
56
56
|
"""Collection of inventories for several devices."""
|
|
57
57
|
|
|
58
|
-
elements: Annotated[
|
|
58
|
+
elements: Annotated[list[Inventory], Field(min_length=1)]
|
|
59
59
|
|
|
60
60
|
def to_csv(self, path: Path) -> None:
|
|
61
61
|
"""Generate a CSV.
|
|
@@ -82,8 +82,8 @@ class InventoryList(ShpModel):
|
|
|
82
82
|
if (_e.created.timestamp() - ts_earl) > 10:
|
|
83
83
|
warnings["time_delta"] = f"[{self.hostname}] time-sync has failed"
|
|
84
84
|
|
|
85
|
-
# turn
|
|
86
|
-
# to
|
|
85
|
+
# turn dict[hostname][type] = val
|
|
86
|
+
# to dict[type][val] = list[hostnames]
|
|
87
87
|
_inp = {
|
|
88
88
|
_e.hostname: _e.model_dump(exclude_unset=True, exclude_defaults=True)
|
|
89
89
|
for _e in self.elements
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
import platform
|
|
4
4
|
import subprocess
|
|
5
5
|
import time
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from collections.abc import Sequence
|
|
6
8
|
from datetime import datetime
|
|
7
9
|
from pathlib import Path
|
|
8
|
-
from
|
|
10
|
+
from types import MappingProxyType
|
|
11
|
+
from typing import Any
|
|
9
12
|
from typing import Optional
|
|
10
13
|
|
|
11
14
|
from typing_extensions import Self
|
|
12
15
|
|
|
13
|
-
from
|
|
14
|
-
from
|
|
16
|
+
from shepherd_core.data_models.base.timezone import local_now
|
|
17
|
+
from shepherd_core.logger import logger
|
|
15
18
|
|
|
16
19
|
try:
|
|
17
20
|
import psutil
|
|
@@ -21,7 +24,7 @@ except ImportError:
|
|
|
21
24
|
from pydantic import ConfigDict
|
|
22
25
|
from pydantic.types import PositiveInt
|
|
23
26
|
|
|
24
|
-
from
|
|
27
|
+
from shepherd_core.data_models import ShpModel
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
class SystemInventory(ShpModel):
|
|
@@ -30,7 +33,7 @@ class SystemInventory(ShpModel):
|
|
|
30
33
|
uptime: PositiveInt
|
|
31
34
|
# ⤷ seconds
|
|
32
35
|
timestamp: datetime
|
|
33
|
-
# time_delta: timedelta = timedelta(0) # noqa: ERA001
|
|
36
|
+
# time_delta: timedelta = timedelta(seconds=0) # noqa: ERA001
|
|
34
37
|
# ⤷ lag behind earliest observer, TODO: wrong place
|
|
35
38
|
|
|
36
39
|
system: str
|
|
@@ -44,13 +47,13 @@ class SystemInventory(ShpModel):
|
|
|
44
47
|
|
|
45
48
|
hostname: str
|
|
46
49
|
|
|
47
|
-
interfaces:
|
|
50
|
+
interfaces: Mapping[str, Any] = MappingProxyType({})
|
|
48
51
|
# ⤷ tuple with
|
|
49
52
|
# ip IPvAnyAddress
|
|
50
53
|
# mac MACStr
|
|
51
54
|
|
|
52
|
-
fs_root:
|
|
53
|
-
beagle:
|
|
55
|
+
fs_root: Sequence[str] = ()
|
|
56
|
+
beagle: Sequence[str] = ()
|
|
54
57
|
|
|
55
58
|
model_config = ConfigDict(str_min_length=0)
|
|
56
59
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"""Hardware related inventory model."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Sequence
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from pydantic import ConfigDict
|
|
7
7
|
from typing_extensions import Self
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from shepherd_core.data_models import ShpModel
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class TargetInventory(ShpModel):
|
|
13
13
|
"""Hardware related inventory model."""
|
|
14
14
|
|
|
15
15
|
cape: Optional[str] = None
|
|
16
|
-
targets:
|
|
16
|
+
targets: Sequence[str] = ()
|
|
17
17
|
|
|
18
18
|
model_config = ConfigDict(str_min_length=0)
|
|
19
19
|
|
shepherd_core/logger.py
CHANGED
|
@@ -33,8 +33,8 @@ def set_log_verbose_level(log_: Union[logging.Logger, logging.Handler], verbose:
|
|
|
33
33
|
if verbose < 3:
|
|
34
34
|
# reduce log-overhead when not debugging, also more user-friendly exceptions
|
|
35
35
|
logging._srcfile = None # noqa: SLF001
|
|
36
|
-
logging.logThreads =
|
|
37
|
-
logging.logProcesses =
|
|
36
|
+
logging.logThreads = False
|
|
37
|
+
logging.logProcesses = False
|
|
38
38
|
|
|
39
39
|
if verbose > 2:
|
|
40
40
|
chromalog.basicConfig(format="%(name)s %(levelname)s: %(message)s")
|