shepherd-core 2025.4.1__py3-none-any.whl → 2025.4.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/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/energy_environment.py +4 -3
- shepherd_core/data_models/content/firmware.py +9 -7
- shepherd_core/data_models/content/virtual_harvester.py +30 -22
- shepherd_core/data_models/content/virtual_source.py +16 -15
- shepherd_core/data_models/experiment/experiment.py +13 -13
- shepherd_core/data_models/experiment/observer_features.py +7 -8
- shepherd_core/data_models/experiment/target_config.py +12 -12
- shepherd_core/data_models/task/__init__.py +5 -5
- shepherd_core/data_models/task/emulation.py +13 -14
- 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 +11 -11
- 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/observer.py +9 -7
- shepherd_core/data_models/testbed/target.py +9 -7
- shepherd_core/data_models/testbed/testbed.py +11 -10
- shepherd_core/decoder_waveform/uart.py +5 -5
- shepherd_core/fw_tools/converter.py +4 -3
- shepherd_core/fw_tools/patcher.py +14 -15
- shepherd_core/fw_tools/validation.py +3 -2
- 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 +37 -39
- shepherd_core/testbed_client/client_abc_fix.py +20 -13
- shepherd_core/testbed_client/client_web.py +18 -11
- shepherd_core/testbed_client/fixtures.py +19 -16
- 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 +6 -5
- shepherd_core/vsource/virtual_source_model.py +6 -5
- shepherd_core/vsource/virtual_source_simulation.py +7 -6
- shepherd_core/writer.py +32 -34
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.4.2.dist-info}/METADATA +2 -3
- shepherd_core-2025.4.2.dist-info/RECORD +81 -0
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.4.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.4.2.dist-info}/top_level.txt +0 -0
- {shepherd_core-2025.4.1.dist-info → shepherd_core-2025.4.2.dist-info}/zip-safe +0 -0
|
@@ -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")
|
shepherd_core/reader.py
CHANGED
|
@@ -9,14 +9,10 @@ import math
|
|
|
9
9
|
import os
|
|
10
10
|
from itertools import product
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from types import MappingProxyType
|
|
12
13
|
from typing import TYPE_CHECKING
|
|
13
14
|
from typing import Any
|
|
14
|
-
from typing import ClassVar
|
|
15
|
-
from typing import Dict
|
|
16
|
-
from typing import Generator
|
|
17
|
-
from typing import List
|
|
18
15
|
from typing import Optional
|
|
19
|
-
from typing import Type
|
|
20
16
|
from typing import Union
|
|
21
17
|
|
|
22
18
|
import h5py
|
|
@@ -26,13 +22,16 @@ from pydantic import validate_call
|
|
|
26
22
|
from tqdm import trange
|
|
27
23
|
from typing_extensions import Self
|
|
28
24
|
|
|
29
|
-
from .commons import
|
|
25
|
+
from .commons import SAMPLERATE_SPS_DEFAULT
|
|
30
26
|
from .data_models.base.calibration import CalibrationPair
|
|
31
27
|
from .data_models.base.calibration import CalibrationSeries
|
|
32
28
|
from .data_models.content.energy_environment import EnergyDType
|
|
33
29
|
from .decoder_waveform import Uart
|
|
34
30
|
|
|
35
31
|
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Generator
|
|
33
|
+
from collections.abc import Mapping
|
|
34
|
+
from collections.abc import Sequence
|
|
36
35
|
from types import TracebackType
|
|
37
36
|
|
|
38
37
|
|
|
@@ -46,28 +45,27 @@ class Reader:
|
|
|
46
45
|
|
|
47
46
|
"""
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
BUFFER_SAMPLES_N: int = 10_000
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
MODE_TO_DTYPE: Mapping[str, Sequence[EnergyDType]] = MappingProxyType(
|
|
51
|
+
{
|
|
52
|
+
"harvester": (
|
|
53
|
+
EnergyDType.ivsample,
|
|
54
|
+
EnergyDType.ivcurve,
|
|
55
|
+
EnergyDType.isc_voc,
|
|
56
|
+
),
|
|
57
|
+
"emulator": (EnergyDType.ivsample,),
|
|
58
|
+
}
|
|
59
|
+
)
|
|
59
60
|
|
|
60
61
|
@validate_call
|
|
61
62
|
def __init__(
|
|
62
63
|
self,
|
|
63
|
-
file_path:
|
|
64
|
+
file_path: Path,
|
|
64
65
|
*,
|
|
65
|
-
verbose:
|
|
66
|
+
verbose: bool = True,
|
|
66
67
|
) -> None:
|
|
67
|
-
|
|
68
|
-
self.file_path: Optional[Path] = None
|
|
69
|
-
if isinstance(file_path, (Path, str)):
|
|
70
|
-
self.file_path = Path(file_path).resolve()
|
|
68
|
+
self.file_path: Path = file_path.resolve()
|
|
71
69
|
|
|
72
70
|
if not hasattr(self, "_logger"):
|
|
73
71
|
self._logger: logging.Logger = logging.getLogger("SHPCore.Reader")
|
|
@@ -75,7 +73,7 @@ class Reader:
|
|
|
75
73
|
self._logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
|
76
74
|
|
|
77
75
|
if not hasattr(self, "samplerate_sps"):
|
|
78
|
-
self.samplerate_sps: int =
|
|
76
|
+
self.samplerate_sps: int = SAMPLERATE_SPS_DEFAULT
|
|
79
77
|
self.sample_interval_ns: int = round(10**9 // self.samplerate_sps)
|
|
80
78
|
self.sample_interval_s: float = 1 / self.samplerate_sps
|
|
81
79
|
|
|
@@ -155,7 +153,7 @@ class Reader:
|
|
|
155
153
|
|
|
156
154
|
def __exit__(
|
|
157
155
|
self,
|
|
158
|
-
typ: Optional[
|
|
156
|
+
typ: Optional[type[BaseException]] = None,
|
|
159
157
|
exc: Optional[BaseException] = None,
|
|
160
158
|
tb: Optional[TracebackType] = None,
|
|
161
159
|
extra_arg: int = 0,
|
|
@@ -183,7 +181,7 @@ class Reader:
|
|
|
183
181
|
self.sample_interval_ns = round(10**9 * self.sample_interval_s)
|
|
184
182
|
self.samplerate_sps = max(round((sample_count - 1) / duration_s), 1)
|
|
185
183
|
self.runtime_s = round(self.ds_voltage.shape[0] / self.samplerate_sps, 1)
|
|
186
|
-
self.buffers_n = int(self.ds_voltage.shape[0] // self.
|
|
184
|
+
self.buffers_n = int(self.ds_voltage.shape[0] // self.BUFFER_SAMPLES_N)
|
|
187
185
|
if isinstance(self.file_path, Path):
|
|
188
186
|
self.file_size = self.file_path.stat().st_size
|
|
189
187
|
else:
|
|
@@ -214,7 +212,7 @@ class Reader:
|
|
|
214
212
|
|
|
215
213
|
"""
|
|
216
214
|
if n_samples_per_buffer is None:
|
|
217
|
-
n_samples_per_buffer = self.
|
|
215
|
+
n_samples_per_buffer = self.BUFFER_SAMPLES_N
|
|
218
216
|
end_max = int(self.ds_voltage.shape[0] // n_samples_per_buffer)
|
|
219
217
|
end_n = end_max if end_n is None else min(end_n, end_max)
|
|
220
218
|
self._logger.debug("Reading blocks %d to %d from source-file", start_n, end_n)
|
|
@@ -254,7 +252,7 @@ class Reader:
|
|
|
254
252
|
return self.h5file.attrs["mode"]
|
|
255
253
|
return ""
|
|
256
254
|
|
|
257
|
-
def get_config(self) ->
|
|
255
|
+
def get_config(self) -> dict:
|
|
258
256
|
if "config" in self.h5file["data"].attrs:
|
|
259
257
|
return yaml.safe_load(self.h5file["data"].attrs["config"])
|
|
260
258
|
return {}
|
|
@@ -329,7 +327,7 @@ class Reader:
|
|
|
329
327
|
self.file_path.name,
|
|
330
328
|
)
|
|
331
329
|
return False
|
|
332
|
-
if self.h5file.attrs["mode"] not in self.
|
|
330
|
+
if self.h5file.attrs["mode"] not in self.MODE_TO_DTYPE:
|
|
333
331
|
self._logger.error(
|
|
334
332
|
"[FileValidation] unsupported mode '%s' in '%s'",
|
|
335
333
|
attr,
|
|
@@ -361,7 +359,7 @@ class Reader:
|
|
|
361
359
|
self.file_path.name,
|
|
362
360
|
)
|
|
363
361
|
return False
|
|
364
|
-
if self.get_datatype() not in self.
|
|
362
|
+
if self.get_datatype() not in self.MODE_TO_DTYPE[self.get_mode()]:
|
|
365
363
|
self._logger.error(
|
|
366
364
|
"[FileValidation] unsupported type '%s' for mode '%s' in '%s'",
|
|
367
365
|
self.get_datatype(),
|
|
@@ -399,7 +397,7 @@ class Reader:
|
|
|
399
397
|
self.file_path.name,
|
|
400
398
|
)
|
|
401
399
|
# dataset-length should be multiple of buffersize
|
|
402
|
-
remaining_size = ds_volt_size % self.
|
|
400
|
+
remaining_size = ds_volt_size % self.BUFFER_SAMPLES_N
|
|
403
401
|
if remaining_size != 0:
|
|
404
402
|
self._logger.warning(
|
|
405
403
|
"[FileValidation] datasets are not aligned with buffer-size in '%s'",
|
|
@@ -478,7 +476,7 @@ class Reader:
|
|
|
478
476
|
|
|
479
477
|
def _dset_statistics(
|
|
480
478
|
self, dset: h5py.Dataset, cal: Optional[CalibrationPair] = None
|
|
481
|
-
) ->
|
|
479
|
+
) -> dict[str, float]:
|
|
482
480
|
"""Create basic stats for a provided dataset.
|
|
483
481
|
|
|
484
482
|
:param dset: dataset to evaluate
|
|
@@ -511,7 +509,7 @@ class Reader:
|
|
|
511
509
|
if len(stats_list) < 1:
|
|
512
510
|
return {}
|
|
513
511
|
stats_nd = np.stack(stats_list)
|
|
514
|
-
stats:
|
|
512
|
+
stats: dict[str, float] = {
|
|
515
513
|
# TODO: wrong calculation for ndim-datasets with n>1
|
|
516
514
|
"mean": float(stats_nd[:, 0].mean()),
|
|
517
515
|
"min": float(stats_nd[:, 1].min()),
|
|
@@ -521,7 +519,7 @@ class Reader:
|
|
|
521
519
|
}
|
|
522
520
|
return stats
|
|
523
521
|
|
|
524
|
-
def _data_timediffs(self) ->
|
|
522
|
+
def _data_timediffs(self) -> list[float]:
|
|
525
523
|
"""Calculate list of unique time-deltas [s] between buffers.
|
|
526
524
|
|
|
527
525
|
Optimized version that only looks at the start of each buffer.
|
|
@@ -540,14 +538,14 @@ class Reader:
|
|
|
540
538
|
|
|
541
539
|
def calc_timediffs(idx_start: int) -> list:
|
|
542
540
|
ds_time = self.ds_time[
|
|
543
|
-
idx_start : (idx_start + self.max_elements) : self.
|
|
541
|
+
idx_start : (idx_start + self.max_elements) : self.BUFFER_SAMPLES_N
|
|
544
542
|
]
|
|
545
543
|
diffs_np = np.unique(ds_time[1:] - ds_time[0:-1], return_counts=False)
|
|
546
544
|
return list(np.array(diffs_np))
|
|
547
545
|
|
|
548
546
|
diffs_ll = [calc_timediffs(i) for i in job_iter]
|
|
549
547
|
diffs = {
|
|
550
|
-
round(self._cal.time.raw_to_si(j) / self.
|
|
548
|
+
round(self._cal.time.raw_to_si(j) / self.BUFFER_SAMPLES_N, 6)
|
|
551
549
|
for i in diffs_ll
|
|
552
550
|
for j in i
|
|
553
551
|
}
|
|
@@ -565,7 +563,7 @@ class Reader:
|
|
|
565
563
|
self._logger.warning(
|
|
566
564
|
"Time-jumps detected -> expected equal steps, but got: %s s", diffs
|
|
567
565
|
)
|
|
568
|
-
return (len(diffs) <= 1) and diffs[0] == round(0.1 / self.
|
|
566
|
+
return (len(diffs) <= 1) and diffs[0] == round(0.1 / self.BUFFER_SAMPLES_N, 6)
|
|
569
567
|
|
|
570
568
|
def count_errors_in_log(self, group_name: str = "sheep", min_level: int = 40) -> int:
|
|
571
569
|
if group_name not in self.h5file:
|
|
@@ -583,7 +581,7 @@ class Reader:
|
|
|
583
581
|
node: Union[h5py.Dataset, h5py.Group, None] = None,
|
|
584
582
|
*,
|
|
585
583
|
minimal: bool = False,
|
|
586
|
-
) ->
|
|
584
|
+
) -> dict[str, dict]:
|
|
587
585
|
"""Recursive FN to capture the structure of the file.
|
|
588
586
|
|
|
589
587
|
:param node: starting node, leave free to go through whole file
|
|
@@ -594,7 +592,7 @@ class Reader:
|
|
|
594
592
|
self._refresh_file_stats()
|
|
595
593
|
return self.get_metadata(self.h5file, minimal=minimal)
|
|
596
594
|
|
|
597
|
-
metadata:
|
|
595
|
+
metadata: dict[str, dict] = {}
|
|
598
596
|
if isinstance(node, h5py.Dataset) and not minimal:
|
|
599
597
|
metadata["_dataset_info"] = {
|
|
600
598
|
"datatype": str(node.dtype),
|
|
@@ -616,7 +614,7 @@ class Reader:
|
|
|
616
614
|
with contextlib.suppress(yaml.YAMLError):
|
|
617
615
|
attr_value = yaml.safe_load(attr_value)
|
|
618
616
|
elif "int" in str(type(attr_value)):
|
|
619
|
-
# TODO: why not isinstance? can it be
|
|
617
|
+
# TODO: why not isinstance? can it be list[int] other complex type?
|
|
620
618
|
attr_value = int(attr_value)
|
|
621
619
|
else:
|
|
622
620
|
attr_value = float(attr_value)
|
|
@@ -675,7 +673,7 @@ class Reader:
|
|
|
675
673
|
return data != data_1
|
|
676
674
|
|
|
677
675
|
def gpio_to_waveforms(self, name: Optional[str] = None) -> dict:
|
|
678
|
-
waveforms:
|
|
676
|
+
waveforms: dict[str, np.ndarray] = {}
|
|
679
677
|
if "gpio" not in self.h5file:
|
|
680
678
|
return waveforms
|
|
681
679
|
|
|
@@ -17,11 +17,12 @@ TODO: Comfort functions missing
|
|
|
17
17
|
|
|
18
18
|
from abc import ABC
|
|
19
19
|
from abc import abstractmethod
|
|
20
|
-
from typing import
|
|
20
|
+
from typing import Any
|
|
21
21
|
from typing import Optional
|
|
22
22
|
|
|
23
|
-
from
|
|
24
|
-
from
|
|
23
|
+
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
24
|
+
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
25
|
+
|
|
25
26
|
from .fixtures import Fixtures
|
|
26
27
|
|
|
27
28
|
|
|
@@ -40,11 +41,11 @@ class AbcClient(ABC):
|
|
|
40
41
|
"""
|
|
41
42
|
|
|
42
43
|
@abstractmethod
|
|
43
|
-
def query_ids(self, model_type: str) ->
|
|
44
|
+
def query_ids(self, model_type: str) -> list[int]:
|
|
44
45
|
pass
|
|
45
46
|
|
|
46
47
|
@abstractmethod
|
|
47
|
-
def query_names(self, model_type: str) ->
|
|
48
|
+
def query_names(self, model_type: str) -> list[str]:
|
|
48
49
|
pass
|
|
49
50
|
|
|
50
51
|
@abstractmethod
|
|
@@ -54,11 +55,15 @@ class AbcClient(ABC):
|
|
|
54
55
|
pass
|
|
55
56
|
|
|
56
57
|
@abstractmethod
|
|
57
|
-
def try_inheritance(
|
|
58
|
+
def try_inheritance(
|
|
59
|
+
self, model_type: str, values: dict[str, Any]
|
|
60
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
58
61
|
# TODO: maybe internal? yes
|
|
59
62
|
pass
|
|
60
63
|
|
|
61
|
-
def try_completing_model(
|
|
64
|
+
def try_completing_model(
|
|
65
|
+
self, model_type: str, values: dict[str, Any]
|
|
66
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
62
67
|
"""Init by name/id, for none existing instances raise Exception.
|
|
63
68
|
|
|
64
69
|
This is the main entry-point for querying a model (used be the core-lib).
|
|
@@ -73,7 +78,7 @@ class AbcClient(ABC):
|
|
|
73
78
|
return self.try_inheritance(model_type, values)
|
|
74
79
|
|
|
75
80
|
@abstractmethod
|
|
76
|
-
def fill_in_user_data(self, values: dict) -> dict:
|
|
81
|
+
def fill_in_user_data(self, values: dict[str, Any]) -> dict[str, Any]:
|
|
77
82
|
# TODO: is it really helpful and needed?
|
|
78
83
|
pass
|
|
79
84
|
|
|
@@ -83,7 +88,7 @@ class FixturesClient(AbcClient):
|
|
|
83
88
|
|
|
84
89
|
def __init__(self) -> None:
|
|
85
90
|
super().__init__()
|
|
86
|
-
self._fixtures:
|
|
91
|
+
self._fixtures: Fixtures = Fixtures()
|
|
87
92
|
|
|
88
93
|
def insert(self, data: ShpModel) -> bool:
|
|
89
94
|
wrap = Wrapper(
|
|
@@ -93,10 +98,10 @@ class FixturesClient(AbcClient):
|
|
|
93
98
|
self._fixtures.insert_model(wrap)
|
|
94
99
|
return True
|
|
95
100
|
|
|
96
|
-
def query_ids(self, model_type: str) ->
|
|
101
|
+
def query_ids(self, model_type: str) -> list[int]:
|
|
97
102
|
return list(self._fixtures[model_type].elements_by_id.keys())
|
|
98
103
|
|
|
99
|
-
def query_names(self, model_type: str) ->
|
|
104
|
+
def query_names(self, model_type: str) -> list[str]:
|
|
100
105
|
return list(self._fixtures[model_type].elements_by_name.keys())
|
|
101
106
|
|
|
102
107
|
def query_item(
|
|
@@ -108,10 +113,12 @@ class FixturesClient(AbcClient):
|
|
|
108
113
|
return self._fixtures[model_type].query_name(name)
|
|
109
114
|
raise ValueError("Query needs either uid or name of object")
|
|
110
115
|
|
|
111
|
-
def try_inheritance(
|
|
116
|
+
def try_inheritance(
|
|
117
|
+
self, model_type: str, values: dict[str, Any]
|
|
118
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
112
119
|
return self._fixtures[model_type].inheritance(values)
|
|
113
120
|
|
|
114
|
-
def fill_in_user_data(self, values: dict) -> dict:
|
|
121
|
+
def fill_in_user_data(self, values: dict[str, Any]) -> dict[str, Any]:
|
|
115
122
|
"""Add fake user-data when offline-client is used.
|
|
116
123
|
|
|
117
124
|
Hotfix until WebClient is working.
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
from importlib import import_module
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any
|
|
6
6
|
from typing import Optional
|
|
7
7
|
from typing import Union
|
|
8
8
|
|
|
9
9
|
from pydantic import validate_call
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
11
|
+
from shepherd_core.commons import TESTBED_SERVER_URI
|
|
12
|
+
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
13
|
+
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
14
|
+
|
|
14
15
|
from .client_abc_fix import AbcClient
|
|
15
16
|
from .user_model import User
|
|
16
17
|
|
|
@@ -37,7 +38,7 @@ class WebClient(AbcClient):
|
|
|
37
38
|
if not hasattr(self, "_token"):
|
|
38
39
|
# add default values
|
|
39
40
|
self._token: str = "basic_public_access" # noqa: S105
|
|
40
|
-
self._server: str =
|
|
41
|
+
self._server: str = TESTBED_SERVER_URI
|
|
41
42
|
self._user: Optional[User] = None
|
|
42
43
|
self._key: Optional[str] = None
|
|
43
44
|
self._connected: bool = False
|
|
@@ -49,6 +50,8 @@ class WebClient(AbcClient):
|
|
|
49
50
|
# ABC Functions below
|
|
50
51
|
|
|
51
52
|
def insert(self, data: ShpModel) -> bool:
|
|
53
|
+
if self._req is None:
|
|
54
|
+
return False
|
|
52
55
|
wrap = Wrapper(
|
|
53
56
|
datatype=type(data).__name__,
|
|
54
57
|
parameters=data.model_dump(),
|
|
@@ -57,10 +60,10 @@ class WebClient(AbcClient):
|
|
|
57
60
|
r.raise_for_status()
|
|
58
61
|
return True
|
|
59
62
|
|
|
60
|
-
def query_ids(self, model_type: str) ->
|
|
63
|
+
def query_ids(self, model_type: str) -> list[int]:
|
|
61
64
|
raise NotImplementedError("TODO")
|
|
62
65
|
|
|
63
|
-
def query_names(self, model_type: str) ->
|
|
66
|
+
def query_names(self, model_type: str) -> list[str]:
|
|
64
67
|
raise NotImplementedError("TODO")
|
|
65
68
|
|
|
66
69
|
def query_item(
|
|
@@ -68,10 +71,14 @@ class WebClient(AbcClient):
|
|
|
68
71
|
) -> dict:
|
|
69
72
|
raise NotImplementedError("TODO")
|
|
70
73
|
|
|
71
|
-
def try_inheritance(
|
|
74
|
+
def try_inheritance(
|
|
75
|
+
self, model_type: str, values: dict[str, Any]
|
|
76
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
72
77
|
raise NotImplementedError("TODO")
|
|
73
78
|
|
|
74
|
-
def fill_in_user_data(self, values: dict) -> dict:
|
|
79
|
+
def fill_in_user_data(self, values: dict[str, Any]) -> dict[str, Any]:
|
|
80
|
+
if self._user is None:
|
|
81
|
+
return values
|
|
75
82
|
if values.get("owner") is None:
|
|
76
83
|
values["owner"] = self._user.name
|
|
77
84
|
if values.get("group") is None:
|
|
@@ -105,7 +112,7 @@ class WebClient(AbcClient):
|
|
|
105
112
|
return self._query_user_data()
|
|
106
113
|
|
|
107
114
|
def _query_session_key(self) -> bool:
|
|
108
|
-
if self._server:
|
|
115
|
+
if self._server and self._req is not None:
|
|
109
116
|
r = self._req.get(self._server + "/session_key", timeout=2)
|
|
110
117
|
r.raise_for_status()
|
|
111
118
|
self._key = r.json()["value"] # TODO: not finished
|
|
@@ -113,7 +120,7 @@ class WebClient(AbcClient):
|
|
|
113
120
|
return False
|
|
114
121
|
|
|
115
122
|
def _query_user_data(self) -> bool:
|
|
116
|
-
if self._server:
|
|
123
|
+
if self._server and self._req is not None:
|
|
117
124
|
r = self._req.get(self._server + "/user?token=" + self._token, timeout=2)
|
|
118
125
|
# TODO: possibly a security nightmare (send via json or encrypted via public key?)
|
|
119
126
|
r.raise_for_status()
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
import pickle
|
|
5
|
+
from collections.abc import Mapping
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from datetime import timedelta
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
|
-
from typing import Dict
|
|
10
|
-
from typing import Mapping
|
|
11
10
|
from typing import Optional
|
|
12
11
|
from typing import Union
|
|
13
12
|
|
|
@@ -15,10 +14,11 @@ import yaml
|
|
|
15
14
|
from pydantic import validate_call
|
|
16
15
|
from typing_extensions import Self
|
|
17
16
|
|
|
18
|
-
from
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
17
|
+
from shepherd_core.data_models.base.timezone import local_now
|
|
18
|
+
from shepherd_core.data_models.base.timezone import local_tz
|
|
19
|
+
from shepherd_core.data_models.base.wrapper import Wrapper
|
|
20
|
+
from shepherd_core.logger import logger
|
|
21
|
+
|
|
22
22
|
from .cache_path import cache_user_path
|
|
23
23
|
|
|
24
24
|
# Proposed field-name:
|
|
@@ -34,8 +34,8 @@ class Fixture:
|
|
|
34
34
|
|
|
35
35
|
def __init__(self, model_type: str) -> None:
|
|
36
36
|
self.model_type: str = model_type.lower()
|
|
37
|
-
self.elements_by_name:
|
|
38
|
-
self.elements_by_id:
|
|
37
|
+
self.elements_by_name: dict[str, dict] = {}
|
|
38
|
+
self.elements_by_id: dict[int, dict] = {}
|
|
39
39
|
# Iterator reset
|
|
40
40
|
self._iter_index: int = 0
|
|
41
41
|
self._iter_list: list = list(self.elements_by_name.values())
|
|
@@ -43,25 +43,26 @@ class Fixture:
|
|
|
43
43
|
def insert(self, data: Wrapper) -> None:
|
|
44
44
|
# ⤷ TODO: could get easier
|
|
45
45
|
# - when not model_name but class used
|
|
46
|
-
# - use doubleref name->id->data (
|
|
46
|
+
# - use doubleref name->id->data (saves RAM)
|
|
47
47
|
if data.datatype.lower() != self.model_type.lower():
|
|
48
48
|
return
|
|
49
49
|
if "name" not in data.parameters:
|
|
50
50
|
return
|
|
51
51
|
name = str(data.parameters["name"]).lower()
|
|
52
52
|
_id = data.parameters["id"]
|
|
53
|
-
|
|
54
|
-
self.elements_by_name[name] =
|
|
55
|
-
self.elements_by_id[_id] =
|
|
53
|
+
data_model = data.parameters
|
|
54
|
+
self.elements_by_name[name] = data_model
|
|
55
|
+
self.elements_by_id[_id] = data_model
|
|
56
56
|
# update iterator
|
|
57
|
-
self._iter_list
|
|
57
|
+
self._iter_list = list(self.elements_by_name.values())
|
|
58
58
|
|
|
59
59
|
def __getitem__(self, key: Union[str, int]) -> dict:
|
|
60
60
|
if isinstance(key, str):
|
|
61
61
|
key = key.lower()
|
|
62
62
|
if key in self.elements_by_name:
|
|
63
63
|
return self.elements_by_name[key]
|
|
64
|
-
|
|
64
|
+
if key.isdigit():
|
|
65
|
+
key = int(key)
|
|
65
66
|
if key in self.elements_by_id:
|
|
66
67
|
return self.elements_by_id[int(key)]
|
|
67
68
|
msg = f"{self.model_type} '{key}' not found!"
|
|
@@ -85,7 +86,9 @@ class Fixture:
|
|
|
85
86
|
def refs(self) -> dict:
|
|
86
87
|
return {_i["id"]: _i["name"] for _i in self.elements_by_id.values()}
|
|
87
88
|
|
|
88
|
-
def inheritance(
|
|
89
|
+
def inheritance(
|
|
90
|
+
self, values: dict[str, Any], chain: Optional[list[str]] = None
|
|
91
|
+
) -> tuple[dict[str, Any], list[str]]:
|
|
89
92
|
if chain is None:
|
|
90
93
|
chain = []
|
|
91
94
|
values = copy.copy(values)
|
|
@@ -181,7 +184,7 @@ class Fixtures:
|
|
|
181
184
|
self.file_path = Path(__file__).parent.parent.resolve() / "data_models"
|
|
182
185
|
else:
|
|
183
186
|
self.file_path = file_path
|
|
184
|
-
self.components:
|
|
187
|
+
self.components: dict[str, Fixture] = {}
|
|
185
188
|
cache_file = cache_user_path / "fixtures.pickle"
|
|
186
189
|
sheep_detect = Path("/lib/firmware/am335x-pru0-fw").exists()
|
|
187
190
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import secrets
|
|
4
4
|
from hashlib import pbkdf2_hmac
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
from typing import Any
|
|
5
7
|
from typing import Optional
|
|
6
8
|
from typing import Union
|
|
7
9
|
from uuid import uuid4
|
|
@@ -14,11 +16,10 @@ from pydantic import SecretStr
|
|
|
14
16
|
from pydantic import StringConstraints
|
|
15
17
|
from pydantic import model_validator
|
|
16
18
|
from pydantic import validate_call
|
|
17
|
-
from typing_extensions import Annotated
|
|
18
19
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
20
|
+
from shepherd_core.data_models.base.content import NameStr
|
|
21
|
+
from shepherd_core.data_models.base.content import SafeStr
|
|
22
|
+
from shepherd_core.data_models.base.shepherd import ShpModel
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
@validate_call
|
|
@@ -63,7 +64,7 @@ class User(ShpModel):
|
|
|
63
64
|
|
|
64
65
|
@model_validator(mode="before")
|
|
65
66
|
@classmethod
|
|
66
|
-
def query_database(cls, values: dict) -> dict:
|
|
67
|
+
def query_database(cls, values: dict[str, Any]) -> dict[str, Any]:
|
|
67
68
|
# TODO:
|
|
68
69
|
|
|
69
70
|
# post correction
|
shepherd_core/version.py
CHANGED
|
@@ -79,9 +79,9 @@ class DiodeTarget(TargetABC):
|
|
|
79
79
|
|
|
80
80
|
def step(self, voltage_uV: int, *, pwr_good: bool) -> float:
|
|
81
81
|
if pwr_good or not self.ctrl:
|
|
82
|
-
V_CC = voltage_uV * 1e-6
|
|
83
|
-
V_D = V_CC / 2
|
|
84
|
-
I_R = I_D = 0
|
|
82
|
+
V_CC: float = voltage_uV * 1e-6
|
|
83
|
+
V_D: float = V_CC / 2
|
|
84
|
+
I_R = I_D = 0.0
|
|
85
85
|
# there is no direct formular, but this iteration converges fast
|
|
86
86
|
for _ in range(10):
|
|
87
87
|
# low voltages tend to produce log(x<0)=err
|
|
@@ -17,9 +17,9 @@ Compromises:
|
|
|
17
17
|
import math
|
|
18
18
|
from typing import Optional
|
|
19
19
|
|
|
20
|
-
from
|
|
21
|
-
from
|
|
22
|
-
from
|
|
20
|
+
from shepherd_core.data_models import CalibrationEmulator
|
|
21
|
+
from shepherd_core.data_models.content.virtual_source import LUT_SIZE
|
|
22
|
+
from shepherd_core.data_models.content.virtual_source import ConverterPRUConfig
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class PruCalibration:
|