shepherd-core 2025.6.4__py3-none-any.whl → 2025.8.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.
@@ -37,9 +37,15 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
37
37
 
38
38
  # further processing of IV-Samples
39
39
  only_power: bool = False
40
- """ ⤷ reduce file-size by calculating power and automatically discard I&V"""
40
+ """ ⤷ reduce file-size by calculating power and automatically discard I&V
41
+ Caution: increases cpu-utilization on observer - power @ 100 kHz is not recommended
42
+ """
41
43
  samplerate: Annotated[int, Field(ge=10, le=100_000)] = 100_000
42
44
  """ ⤷ reduce file-size by re-sampling (mean over x samples)
45
+
46
+ Caution: this option is available for IV-Samples (not only .only_power)
47
+ but results might be faulty for use-cases that are not const-voltage
48
+ Sum(U * I) != Sum(U) * Sum(I)
43
49
  Timestamps will be taken from the start of that sample-window.
44
50
  example: 10 Hz samplerate will be binning 10k samples via mean() and
45
51
  the timestamp for that new sample will be value[0] of the 10k long vector
@@ -148,7 +154,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
148
154
  See doc for nRF_FRAM_Target_v1.3+ to see mapping of target port.
149
155
 
150
156
  Example for skipping UART (pin 0 & 1):
151
- .gpio = range(2,18)
157
+ .gpios = range(2,18)
152
158
 
153
159
  Note:
154
160
  - Cape 2.4 (2023) and lower only has 9x GPIO + 1x PwrGood
@@ -35,8 +35,8 @@ from .helper_paths import path_posix
35
35
  class Compression(str, Enum):
36
36
  """Options for choosing a dataset-compression."""
37
37
 
38
- lzf = default = "lzf" # not native hdf5
39
- gzip1 = gzip = 1 # higher compr & load
38
+ lzf = "lzf" # not native hdf5
39
+ gzip1 = gzip = default = 1 # higher compr & load
40
40
  null = None
41
41
  # NOTE: lzf & external file-compression (xz or zstd) work better than gzip
42
42
  # -> even with additional compression
@@ -127,14 +127,13 @@ class EmulationTask(ShpModel):
127
127
 
128
128
  @model_validator(mode="after")
129
129
  def post_validation(self) -> Self:
130
- # TODO: limit paths
131
- time_now = datetime.now().astimezone()
130
+ time_now = datetime.now().astimezone() - timedelta(hours=24)
132
131
  if self.time_start is not None and self.time_start < time_now:
133
132
  msg = (
134
133
  "Start-Time for Emulation can't be in the past "
135
134
  f"('{self.time_start}' vs '{time_now}'."
136
135
  )
137
- raise ValueError(msg)
136
+ log.error(msg) # do not raise anymore - server & clients do not have to match
138
137
  if self.duration and self.duration.total_seconds() < 0:
139
138
  raise ValueError("Task-Duration can't be negative.")
140
139
  if isinstance(self.voltage_aux, str) and self.voltage_aux not in {
@@ -181,6 +180,10 @@ class EmulationTask(ShpModel):
181
180
  )
182
181
 
183
182
  def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
183
+ """Limit paths to allowed directories.
184
+
185
+ TODO: could be added to validator (with a switch)
186
+ """
184
187
  all_ok = any(self.input_path.is_relative_to(path) for path in paths)
185
188
  all_ok &= any(self.output_path.is_relative_to(path) for path in paths)
186
189
  return all_ok
@@ -93,6 +93,7 @@ class FirmwareModTask(ShpModel):
93
93
  return cls(**kwargs)
94
94
 
95
95
  def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
96
+ """Limit paths to allowed directories."""
96
97
  all_ok = any(self.firmware_file.is_relative_to(path) for path in paths)
97
98
  if isinstance(self.data, Path):
98
99
  all_ok = any(self.data.is_relative_to(path) for path in paths)
@@ -72,12 +72,11 @@ class HarvestTask(ShpModel):
72
72
  @model_validator(mode="after")
73
73
  def post_validation(self) -> Self:
74
74
  # TODO: limit paths
75
- has_time = self.time_start is not None
75
+ has_time = False # TODO: deactivated, self.time_start is not None
76
76
  time_now = datetime.now().astimezone()
77
77
  if has_time and self.time_start < time_now:
78
78
  msg = (
79
- "Start-Time for Emulation can't be in the past "
80
- f"('{self.time_start}' vs '{time_now}'."
79
+ f"Start-Time for Harvest can't be in the past ('{self.time_start}' vs '{time_now}'."
81
80
  )
82
81
  raise ValueError(msg)
83
82
  if self.duration and self.duration.total_seconds() < 0:
@@ -85,4 +84,5 @@ class HarvestTask(ShpModel):
85
84
  return self
86
85
 
87
86
  def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
87
+ """Limit paths to allowed directories."""
88
88
  return any(self.output_path.is_relative_to(path) for path in paths)
@@ -52,12 +52,14 @@ class ObserverTasks(ShpModel):
52
52
 
53
53
  @classmethod
54
54
  @validate_call
55
- def from_xp(cls, xp: Experiment, tb: Testbed, tgt_id: IdInt) -> Self:
55
+ def from_xp(cls, xp: Experiment, xp_folder: str | None, tb: Testbed, tgt_id: IdInt) -> Self:
56
56
  if not tb.shared_storage:
57
57
  raise ValueError("Implementation currently relies on shared storage!")
58
58
 
59
59
  obs = tb.get_observer(tgt_id)
60
- root_path = tb.data_on_observer / "experiments" / xp.folder_name()
60
+ if xp_folder is None:
61
+ xp_folder = xp.folder_name() # moved a layer up for consistent naming
62
+ root_path = tb.data_on_observer / "experiments" / xp_folder
61
63
  fw_paths = [root_path / f"fw{_i}_{obs.name}.hex" for _i in [1, 2]]
62
64
 
63
65
  return cls(
@@ -93,6 +95,7 @@ class ObserverTasks(ShpModel):
93
95
  return values
94
96
 
95
97
  def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
98
+ """Limit paths to allowed directories."""
96
99
  all_ok = any(self.root_path.is_relative_to(path) for path in paths)
97
100
  all_ok &= self.fw1_mod.is_contained(paths)
98
101
  all_ok &= self.fw2_mod.is_contained(paths)
@@ -78,4 +78,5 @@ class ProgrammingTask(ShpModel):
78
78
  )
79
79
 
80
80
  def is_contained(self, paths: AbstractSet[PurePosixPath]) -> bool:
81
+ """Limit paths to allowed directories."""
81
82
  return any(self.firmware_file.is_relative_to(path) for path in paths)
@@ -34,7 +34,8 @@ class TestbedTasks(ShpModel):
34
34
  tb = Testbed() # this will query the first (and only) entry of client
35
35
 
36
36
  tgt_ids = xp.get_target_ids()
37
- obs_tasks = [ObserverTasks.from_xp(xp, tb, _id) for _id in tgt_ids]
37
+ xp_folder = xp.folder_name()
38
+ obs_tasks = [ObserverTasks.from_xp(xp, xp_folder, tb, _id) for _id in tgt_ids]
38
39
  return cls(
39
40
  name=xp.name,
40
41
  observer_tasks=obs_tasks,
@@ -46,6 +47,9 @@ class TestbedTasks(ShpModel):
46
47
  return tasks
47
48
  return None
48
49
 
50
+ def get_observers(self) -> set[str]:
51
+ return {tasks.observer for tasks in self.observer_tasks}
52
+
49
53
  def get_output_paths(self) -> dict[str, Path]:
50
54
  # TODO: computed field preferred, but they don't work here, as
51
55
  # - they are always stored in yaml despite "repr=False"
@@ -56,6 +60,7 @@ class TestbedTasks(ShpModel):
56
60
  return values
57
61
 
58
62
  def is_contained(self) -> bool:
63
+ """Limit paths to allowed directories."""
59
64
  paths_allowed: AbstractSet[PurePosixPath] = {
60
65
  PurePosixPath("/var/shepherd/"),
61
66
  PurePosixPath("/tmp/"), # noqa: S108
@@ -50,6 +50,7 @@ class Uart:
50
50
  def __init__(
51
51
  self,
52
52
  content: Path | np.ndarray,
53
+ *,
53
54
  baud_rate: int | None = None,
54
55
  frame_length: int | None = 8,
55
56
  inversion: bool | None = None,
@@ -1,6 +1,8 @@
1
1
  """Read and modify symbols in ELF-files."""
2
2
 
3
+ import shutil
3
4
  from pathlib import Path
5
+ from tempfile import TemporaryDirectory
4
6
  from typing import Annotated
5
7
 
6
8
  from pydantic import Field
@@ -28,22 +30,27 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
28
30
  return False
29
31
  if ELF is None:
30
32
  raise RuntimeError(elf_error_text)
31
- elf = ELF(path=file_elf)
32
- try:
33
- addr = elf.symbols[symbol]
34
- except KeyError:
35
- addr = None
36
- if addr is None:
37
- log.debug("Symbol '%s' not found in ELF-File %s", symbol, file_elf.name)
38
- return False
39
- log.debug(
40
- "Symbol '%s' found in ELF-File %s, arch=%s, order=%s",
41
- symbol,
42
- file_elf.name,
43
- elf.arch,
44
- elf.endian,
45
- )
46
- elf.close()
33
+ with TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
34
+ # switcheroo that might prevent windows bug - overwrite fails in modify_symbol_value()
35
+ file_tmp = Path(tmp) / file_elf.name
36
+ shutil.copy(file_elf, file_tmp)
37
+ elf = ELF(path=file_tmp)
38
+ try:
39
+ addr = elf.symbols[symbol]
40
+ except KeyError:
41
+ addr = None
42
+ if addr is None:
43
+ elf.close() # better be safe
44
+ log.debug("Symbol '%s' not found in ELF-File %s", symbol, file_elf.name)
45
+ return False
46
+ log.debug(
47
+ "Symbol '%s' found in ELF-File %s, arch=%s, order=%s",
48
+ symbol,
49
+ file_elf.name,
50
+ elf.arch,
51
+ elf.endian,
52
+ )
53
+ elf.close()
47
54
  return True
48
55
 
49
56
 
@@ -60,8 +67,9 @@ def read_symbol(file_elf: Path, symbol: str, length: int) -> int | None:
60
67
  elf = ELF(path=file_elf)
61
68
  addr = elf.symbols[symbol]
62
69
  value_raw = elf.read(address=addr, count=length)[-length:]
70
+ endian = elf.endian
63
71
  elf.close()
64
- return int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
72
+ return int.from_bytes(bytes=value_raw, byteorder=endian, signed=False)
65
73
 
66
74
 
67
75
  def read_uid(file_elf: Path) -> int | None:
@@ -76,8 +84,11 @@ def read_arch(file_elf: Path) -> str | None:
76
84
  if ELF is None:
77
85
  raise RuntimeError(elf_error_text)
78
86
  elf = ELF(path=file_elf)
79
- if "exec" in elf.elftype.lower():
80
- return elf.arch.lower()
87
+ elf_type = elf.elftype.lower()
88
+ elf_arch = elf.arch.lower()
89
+ elf.close()
90
+ if "exec" in elf_type:
91
+ return elf_arch
81
92
  log.error("ELF is not Executable")
82
93
  return None
83
94
 
@@ -102,22 +113,37 @@ def modify_symbol_value(
102
113
  return None
103
114
  if ELF is None:
104
115
  raise RuntimeError(elf_error_text)
105
- elf = ELF(path=file_elf)
106
- addr = elf.symbols[symbol]
107
- value_raw = elf.read(address=addr, count=config.UID_SIZE)[-config.UID_SIZE :]
108
- # ⤷ cutting needed -> msp produces 4b instead of 2
109
- value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
110
- value_raw = value.to_bytes(length=config.UID_SIZE, byteorder=elf.endian, signed=False)
111
-
112
- try:
113
- elf.write(address=addr, data=value_raw)
114
- except AttributeError:
115
- log.warning("ELF-Modifier failed @%s for symbol '%s'", f"0x{addr:X}", symbol)
116
- return None
116
+ with TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
117
+ # switcheroo that also prevents windows bug (overwrite fails)
118
+ file_tmp = Path(tmp) / file_elf.name
119
+ shutil.copy(file_elf, file_tmp)
120
+
121
+ elf = ELF(path=file_elf)
122
+ addr = elf.symbols[symbol]
123
+ value_raw = elf.read(address=addr, count=config.UID_SIZE)[-config.UID_SIZE :]
124
+ # ⤷ cutting needed -> msp produces 4b instead of 2
125
+ value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
126
+ value_raw = value.to_bytes(length=config.UID_SIZE, byteorder=elf.endian, signed=False)
127
+
128
+ try:
129
+ elf.write(address=addr, data=value_raw)
130
+ except AttributeError:
131
+ log.warning("ELF-Modifier failed @%s for symbol '%s'", f"0x{addr:X}", symbol)
132
+ elf.close()
133
+ return None
134
+
135
+ file_new = file_elf if overwrite else file_elf.with_stem(file_elf.stem + "_" + str(value))
136
+ try:
137
+ file_new.unlink(missing_ok=True)
138
+ except PermissionError:
139
+ elf.close()
140
+ log.error(
141
+ "Failed to overwrite file, because it's somehow still in use (typical for WinOS)."
142
+ )
143
+ return None
144
+ elf.save(path=file_new)
145
+ elf.close()
117
146
 
118
- file_new = file_elf if overwrite else file_elf.with_stem(file_elf.stem + "_" + str(value))
119
- elf.save(path=file_new)
120
- elf.close()
121
147
  log.debug(
122
148
  "Value of Symbol '%s' modified: %s -> %s @%s",
123
149
  symbol,
@@ -5,6 +5,7 @@ TODO: Work in Progress.
5
5
  - detection-functions that register in main validator.
6
6
  """
7
7
 
8
+ import shutil
8
9
  import tempfile
9
10
  from pathlib import Path
10
11
 
@@ -92,7 +93,12 @@ def is_elf(file: Path) -> bool:
92
93
  if not file.is_file():
93
94
  return False
94
95
  try:
95
- _ = ELF(path=file)
96
+ with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmp:
97
+ # switcheroo that might prevent windows bug - overwrite fails in modify_symbol_value()
98
+ file_tmp = Path(tmp) / file.name
99
+ shutil.copy(file, file_tmp)
100
+ elf = ELF(path=file_tmp)
101
+ elf.close()
96
102
  except ELFError:
97
103
  log.debug("File %s is not ELF - Magic number does not match", file.name)
98
104
  return False
shepherd_core/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """Separated string avoids circular imports."""
2
2
 
3
- version: str = "2025.06.4"
3
+ version: str = "2025.08.1"
@@ -65,7 +65,7 @@ def simulate_source(
65
65
  stats_internal = np.empty((round(file_inp.runtime_s * file_inp.samplerate_sps), 11))
66
66
  try:
67
67
  # keep dependencies low
68
- from matplotlib import pyplot as plt
68
+ from matplotlib import pyplot as plt # noqa: PLC0415
69
69
  except ImportError:
70
70
  log.warning("Matplotlib not installed, plotting of internals disabled")
71
71
  stats_internal = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shepherd_core
3
- Version: 2025.6.4
3
+ Version: 2025.8.1
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>
@@ -43,15 +43,12 @@ Requires-Dist: pwntools-elf-only; extra == "elf"
43
43
  Provides-Extra: inventory
44
44
  Requires-Dist: psutil; extra == "inventory"
45
45
  Provides-Extra: dev
46
- Requires-Dist: twine; extra == "dev"
47
- Requires-Dist: pre-commit; extra == "dev"
48
- Requires-Dist: pyright; extra == "dev"
49
- Requires-Dist: ruff; extra == "dev"
50
- Requires-Dist: mypy; extra == "dev"
51
46
  Requires-Dist: types-PyYAML; extra == "dev"
52
47
  Provides-Extra: test
53
48
  Requires-Dist: pytest; extra == "test"
54
49
  Requires-Dist: coverage; extra == "test"
50
+ Provides-Extra: all
51
+ Requires-Dist: shepherd-core[dev,elf,inventory,test]; extra == "all"
55
52
 
56
53
  # Core Library
57
54
 
@@ -4,7 +4,7 @@ shepherd_core/commons.py,sha256=_phovuhCgLmO5gcazQ5hyykUPc907dyK9KpY2lUtoIM,205
4
4
  shepherd_core/config.py,sha256=YegFEXuBUBnbq5mb67em8ozEnSkEQSPXjqHlKA2HXCQ,967
5
5
  shepherd_core/logger.py,sha256=Plx7JFZXSYWAKbeOTyo7uApe3sDBKEQhG4ovhiET9Sc,1772
6
6
  shepherd_core/reader.py,sha256=lFpcsnia72vGrR0othUZxh_pn_cM_9sbjzZRywjXus0,29246
7
- shepherd_core/version.py,sha256=MnQ5sTXfw9kncBl7S6djzJEU8gMQaI_BCLsZeqaoU9A,76
7
+ shepherd_core/version.py,sha256=iMNehsT3f5S69B39uUAurKGE93x8sHgh9iUY_vqvVt8,76
8
8
  shepherd_core/writer.py,sha256=W4jWCQ2br_iJHvgknnuCThC5JQ9p5VxsWhsMnviOGHY,14489
9
9
  shepherd_core/data_models/__init__.py,sha256=jBsk2RpD5Gw5GNe1gql9YrWD-7Uv7F2jwRRx-CHdceQ,1909
10
10
  shepherd_core/data_models/readme.md,sha256=DHPVmkWqDksWomRHRTVWVHy9wXF9oMJrITgKs4Pnz2g,2494
@@ -28,16 +28,16 @@ shepherd_core/data_models/content/virtual_source.py,sha256=GbdRghRLmLEUrMo1dyd0P
28
28
  shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=WWbo9ACoD-JJ-jidMFTfwSn4PR_nRPwKQ0Aa2qKVrxE,11202
29
29
  shepherd_core/data_models/experiment/__init__.py,sha256=lorsx0M-JWPIrt_UZfexsLwaITv5slFb3krBOt0idm8,618
30
30
  shepherd_core/data_models/experiment/experiment.py,sha256=6fv-UMIFweWHtJT4KxGcIrqk2EY8MKi0SXZQj0EADHo,4268
31
- shepherd_core/data_models/experiment/observer_features.py,sha256=3Ex92yHzfiJ-O1QldgfUGBZTDG6NoRmrmtxY0IJxsos,8029
31
+ shepherd_core/data_models/experiment/observer_features.py,sha256=culZr3_PXHas5SL9prSKCGLEyzd3mfVj6jDnYCKDy6I,8341
32
32
  shepherd_core/data_models/experiment/target_config.py,sha256=l0_TsE5UYLVe3E8ygsLQdG-KC5v2TVcP3vjDf8Fu_ek,4210
33
33
  shepherd_core/data_models/task/__init__.py,sha256=lW-U3Ativerhan_8JMlNSgvHvvS6PH02zmTJviLYoNg,3633
34
- shepherd_core/data_models/task/emulation.py,sha256=MEsC42dVlEyG-TAVw84fRETZaCc0X1Ny0gVer5pshro,7697
35
- shepherd_core/data_models/task/firmware_mod.py,sha256=rke3WO69YKq0BLoFttwXqueweM9wEplsQ3GS6BeDDtA,3422
36
- shepherd_core/data_models/task/harvest.py,sha256=aefKeI5zx2OrputmT1_ooq0bcOKr5DF_-fMgSM7sZ_w,3659
34
+ shepherd_core/data_models/task/emulation.py,sha256=N--FYDHdBFaCcwCXn1p66xorSyKuobS7Vy50WrxutJw,7866
35
+ shepherd_core/data_models/task/firmware_mod.py,sha256=0UjCGi7SZ00B-ggZMowXrRlpV9bYjKjYMHEk23c_3Z0,3472
36
+ shepherd_core/data_models/task/harvest.py,sha256=10xflQ9EPgB8Q2SBrymXFIDJyikTl4-PVwiCoaPYpV0,3716
37
37
  shepherd_core/data_models/task/helper_paths.py,sha256=AOfbZekT1OxH8pUV_B0S_SR7O4tcRbJalhnUBGPfvd4,440
38
- shepherd_core/data_models/task/observer_tasks.py,sha256=_jMlyjyikXAiUw5YHl29FlZP4-3tPmO9wo9wkZwDlpc,3812
39
- shepherd_core/data_models/task/programming.py,sha256=h1mS4x6x1SPzGyxpQcfpMF-JKofvdP4pum1YovLXUTE,2882
40
- shepherd_core/data_models/task/testbed_tasks.py,sha256=_mDctCGyg25LgsAfFq05Bo-YaJALCVfXXF0dSrb7JYc,2269
38
+ shepherd_core/data_models/task/observer_tasks.py,sha256=udkAEfOjYnpDONHeZ5iPnM4qnARQMH_M_t8LG2UHSEI,3991
39
+ shepherd_core/data_models/task/programming.py,sha256=zJ84oW-bchu4CNPK8z3dDQvBNc5FAtUI8bbXd3s2zOY,2932
40
+ shepherd_core/data_models/task/testbed_tasks.py,sha256=cweCmfiQZ364BZ_9y7MCjyL-5yJxQ5A2zCXwPtViUi0,2474
41
41
  shepherd_core/data_models/testbed/__init__.py,sha256=t9nwml5pbu7ZWghimOyZ8ujMIgnRgFkl23pNb5d_KdU,581
42
42
  shepherd_core/data_models/testbed/cape.py,sha256=F4BOYrzgT1u-8A7seaEJsxwi_VZHsLJBXCnyC55Ok-Y,1329
43
43
  shepherd_core/data_models/testbed/cape_fixture.yaml,sha256=ZCjQSlHE3_5EQpusmRYuw-z9NlxT-8MU49RCd04PfAg,2373
@@ -53,12 +53,12 @@ shepherd_core/data_models/testbed/target_fixture.yaml,sha256=aoP7Al_sXw8nBQpIP25
53
53
  shepherd_core/data_models/testbed/testbed.py,sha256=e2szb1vFG0aiYMaYOT5Y1y1Y8mqvgnoP3oq9DcQgSGk,3727
54
54
  shepherd_core/data_models/testbed/testbed_fixture.yaml,sha256=ca5LI-fWoc3I9m2QScVAh84Bv-ftkSGAizR3ZR0lkC8,980
55
55
  shepherd_core/decoder_waveform/__init__.py,sha256=-ohGz0fA2tKxUJk4FAQXKtI93d6YGdy0CrkdhOod1QU,120
56
- shepherd_core/decoder_waveform/uart.py,sha256=ZMDA9-UL7FCjEIoVrdfeQ_thtXdckNDJF-STd5GH05E,10974
56
+ shepherd_core/decoder_waveform/uart.py,sha256=oeGwwXOrc_36pc0vDW8m3cwkkwa8imzXSxcdeqUKX44,10985
57
57
  shepherd_core/fw_tools/__init__.py,sha256=D9GGj9TzLWZfPjG_iV2BsF-Q1TGTYTgEzWTUI5ReVAA,2090
58
58
  shepherd_core/fw_tools/converter.py,sha256=wRJvcaFyMpApUTNBTh3a-vqqfRXDJWYppEaH0oyrACY,3626
59
59
  shepherd_core/fw_tools/converter_elf.py,sha256=DC-zDi1v7pCFTA1d4VqErDvIMAYwAbv_efqh17wLeRA,1058
60
- shepherd_core/fw_tools/patcher.py,sha256=zwcDITrEZ_CESg1uEMQaxM40VJHnwIt_zaK5lcoPPUk,3878
61
- shepherd_core/fw_tools/validation.py,sha256=usX-wifUrDmAh2QNhPv0qn0CFrsdjgkOEcYwcVaTc1A,4786
60
+ shepherd_core/fw_tools/patcher.py,sha256=V5Dg-uhTxT7Ro4UdFS7pVl2GwkKHLWd8YIvZKxMdePg,4941
61
+ shepherd_core/fw_tools/validation.py,sha256=unm6jsj2538EfIAuRcrpnA2dee1-THD5_dKWFVmCmMI,5095
62
62
  shepherd_core/inventory/__init__.py,sha256=yQxP55yV61xXWfZSSzekQQYopPZCspFpHSyG7VTqtpg,3819
63
63
  shepherd_core/inventory/python.py,sha256=uchavGsssM4CTYQ23kF6hxhl3NUeEpGVvJYX8t7dWU4,1210
64
64
  shepherd_core/inventory/system.py,sha256=VHXJjTgImppbYEkAZqBPduDMCQ1z1iYTr3OWKcUJGSc,3168
@@ -75,9 +75,9 @@ shepherd_core/vsource/virtual_converter_model.py,sha256=D9lkvRe6gTm3kEfyLmuBvuxD
75
75
  shepherd_core/vsource/virtual_harvester_model.py,sha256=K9RnAHCz1ibdYMte5l_U7vrY6_mVRZBg7xBL2ZYC16c,9757
76
76
  shepherd_core/vsource/virtual_harvester_simulation.py,sha256=SGAqXb-PC_JblbyBRoYxu-64VKawnixuhsnlv_xk6Mk,2516
77
77
  shepherd_core/vsource/virtual_source_model.py,sha256=VMiDDJYhc5LZIBoi-uHWeXKPnZASqaLMd4FmArj21qw,3126
78
- shepherd_core/vsource/virtual_source_simulation.py,sha256=UcGDY5a2JeL-Hr-A8zfXjeQiEO6K6sWNVl3AWpvgGiU,5236
79
- shepherd_core-2025.6.4.dist-info/METADATA,sha256=DxMxk9kGMXyy3PsgiICLYYoJxI13gqWURkjqUBUmGLU,7729
80
- shepherd_core-2025.6.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
81
- shepherd_core-2025.6.4.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
82
- shepherd_core-2025.6.4.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
83
- shepherd_core-2025.6.4.dist-info/RECORD,,
78
+ shepherd_core/vsource/virtual_source_simulation.py,sha256=HL3Pt4-fZCdg75fXgYhNmnQ8DsGWs01skIfMHJK87XE,5253
79
+ shepherd_core-2025.8.1.dist-info/METADATA,sha256=3XDbI3GGfnicMvDHugP-rVbdtx3XZyy6W6GXArmyAuA,7628
80
+ shepherd_core-2025.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
81
+ shepherd_core-2025.8.1.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
82
+ shepherd_core-2025.8.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
83
+ shepherd_core-2025.8.1.dist-info/RECORD,,