shepherd-core 2023.12.1__py3-none-any.whl → 2024.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.
Files changed (116) hide show
  1. shepherd_core/__init__.py +5 -4
  2. shepherd_core/calibration_hw_def.py +9 -1
  3. shepherd_core/commons.py +2 -0
  4. shepherd_core/data_models/__init__.py +11 -0
  5. shepherd_core/data_models/base/__init__.py +4 -1
  6. shepherd_core/data_models/base/cal_measurement.py +18 -6
  7. shepherd_core/data_models/base/calibration.py +41 -16
  8. shepherd_core/data_models/base/content.py +20 -5
  9. shepherd_core/data_models/base/shepherd.py +23 -12
  10. shepherd_core/data_models/base/timezone.py +5 -0
  11. shepherd_core/data_models/base/wrapper.py +3 -3
  12. shepherd_core/data_models/content/__init__.py +5 -4
  13. shepherd_core/data_models/content/_external_fixtures.yaml +32 -16
  14. shepherd_core/data_models/content/energy_environment.py +7 -5
  15. shepherd_core/data_models/content/energy_environment_fixture.yaml +3 -0
  16. shepherd_core/data_models/content/firmware.py +12 -5
  17. shepherd_core/data_models/content/firmware_datatype.py +7 -0
  18. shepherd_core/data_models/content/virtual_harvester.py +25 -20
  19. shepherd_core/data_models/content/virtual_harvester_fixture.yaml +1 -0
  20. shepherd_core/data_models/content/virtual_source.py +40 -23
  21. shepherd_core/data_models/content/virtual_source_fixture.yaml +1 -0
  22. shepherd_core/data_models/experiment/__init__.py +5 -4
  23. shepherd_core/data_models/experiment/experiment.py +16 -15
  24. shepherd_core/data_models/experiment/observer_features.py +18 -12
  25. shepherd_core/data_models/experiment/target_config.py +11 -7
  26. shepherd_core/data_models/readme.md +88 -0
  27. shepherd_core/data_models/task/__init__.py +10 -3
  28. shepherd_core/data_models/task/emulation.py +9 -6
  29. shepherd_core/data_models/task/firmware_mod.py +4 -2
  30. shepherd_core/data_models/task/harvest.py +5 -4
  31. shepherd_core/data_models/task/observer_tasks.py +4 -2
  32. shepherd_core/data_models/task/programming.py +3 -1
  33. shepherd_core/data_models/task/testbed_tasks.py +10 -4
  34. shepherd_core/data_models/testbed/__init__.py +5 -2
  35. shepherd_core/data_models/testbed/cape.py +8 -6
  36. shepherd_core/data_models/testbed/gpio.py +11 -9
  37. shepherd_core/data_models/testbed/mcu.py +10 -10
  38. shepherd_core/data_models/testbed/observer.py +10 -5
  39. shepherd_core/data_models/testbed/observer_fixture.yaml +23 -22
  40. shepherd_core/data_models/testbed/target.py +5 -3
  41. shepherd_core/data_models/testbed/target_fixture.yaml +11 -11
  42. shepherd_core/data_models/testbed/testbed.py +6 -3
  43. shepherd_core/decoder_waveform/__init__.py +2 -0
  44. shepherd_core/decoder_waveform/uart.py +44 -25
  45. shepherd_core/fw_tools/__init__.py +2 -0
  46. shepherd_core/fw_tools/converter.py +20 -9
  47. shepherd_core/fw_tools/converter_elf.py +3 -0
  48. shepherd_core/fw_tools/patcher.py +16 -4
  49. shepherd_core/fw_tools/validation.py +25 -5
  50. shepherd_core/inventory/__init__.py +66 -6
  51. shepherd_core/inventory/python.py +4 -0
  52. shepherd_core/inventory/system.py +13 -1
  53. shepherd_core/inventory/target.py +4 -0
  54. shepherd_core/logger.py +5 -0
  55. shepherd_core/reader.py +44 -26
  56. shepherd_core/testbed_client/__init__.py +2 -0
  57. shepherd_core/testbed_client/cache_path.py +17 -0
  58. shepherd_core/testbed_client/client.py +14 -8
  59. shepherd_core/testbed_client/fixtures.py +30 -11
  60. shepherd_core/testbed_client/user_model.py +13 -6
  61. shepherd_core/vsource/__init__.py +2 -0
  62. shepherd_core/vsource/virtual_converter_model.py +11 -4
  63. shepherd_core/vsource/virtual_harvester_model.py +8 -1
  64. shepherd_core/vsource/virtual_source_model.py +10 -5
  65. shepherd_core/writer.py +28 -20
  66. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/METADATA +50 -34
  67. shepherd_core-2024.4.2.dist-info/RECORD +75 -0
  68. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/WHEEL +1 -1
  69. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/top_level.txt +0 -1
  70. shepherd_core-2023.12.1.dist-info/RECORD +0 -117
  71. tests/__init__.py +0 -0
  72. tests/conftest.py +0 -64
  73. tests/data_models/__init__.py +0 -0
  74. tests/data_models/conftest.py +0 -14
  75. tests/data_models/example_cal_data.yaml +0 -31
  76. tests/data_models/example_cal_data_faulty.yaml +0 -29
  77. tests/data_models/example_cal_meas.yaml +0 -178
  78. tests/data_models/example_cal_meas_faulty1.yaml +0 -142
  79. tests/data_models/example_cal_meas_faulty2.yaml +0 -136
  80. tests/data_models/example_config_emulator.yaml +0 -41
  81. tests/data_models/example_config_experiment.yaml +0 -16
  82. tests/data_models/example_config_experiment_alternative.yaml +0 -14
  83. tests/data_models/example_config_harvester.yaml +0 -15
  84. tests/data_models/example_config_testbed.yaml +0 -26
  85. tests/data_models/example_config_virtsource.yaml +0 -78
  86. tests/data_models/test_base_models.py +0 -205
  87. tests/data_models/test_content_fixtures.py +0 -41
  88. tests/data_models/test_content_models.py +0 -282
  89. tests/data_models/test_examples.py +0 -48
  90. tests/data_models/test_experiment_models.py +0 -277
  91. tests/data_models/test_task_generation.py +0 -52
  92. tests/data_models/test_task_models.py +0 -131
  93. tests/data_models/test_testbed_fixtures.py +0 -47
  94. tests/data_models/test_testbed_models.py +0 -187
  95. tests/decoder_waveform/__init__.py +0 -0
  96. tests/decoder_waveform/test_decoder.py +0 -34
  97. tests/fw_tools/__init__.py +0 -0
  98. tests/fw_tools/conftest.py +0 -5
  99. tests/fw_tools/test_converter.py +0 -76
  100. tests/fw_tools/test_patcher.py +0 -66
  101. tests/fw_tools/test_validation.py +0 -56
  102. tests/inventory/__init__.py +0 -0
  103. tests/inventory/test_inventory.py +0 -20
  104. tests/test_cal_hw.py +0 -34
  105. tests/test_examples.py +0 -40
  106. tests/test_logger.py +0 -15
  107. tests/test_reader.py +0 -283
  108. tests/test_writer.py +0 -169
  109. tests/testbed_client/__init__.py +0 -0
  110. tests/vsource/__init__.py +0 -0
  111. tests/vsource/conftest.py +0 -49
  112. tests/vsource/test_converter.py +0 -161
  113. tests/vsource/test_harvester.py +0 -73
  114. tests/vsource/test_z.py +0 -5
  115. /shepherd_core/data_models/{doc_virtual_source.py → virtual_source_doc.txt} +0 -0
  116. {shepherd_core-2023.12.1.dist-info → shepherd_core-2024.4.2.dist-info}/zip-safe +0 -0
@@ -1,9 +1,13 @@
1
+ """Config for testbed experiments."""
2
+
1
3
  from datetime import datetime
2
4
  from datetime import timedelta
3
5
  from typing import List
4
6
  from typing import Optional
7
+ from typing import Union
8
+ from uuid import uuid4
5
9
 
6
- from pydantic import EmailStr
10
+ from pydantic import UUID4
7
11
  from pydantic import Field
8
12
  from pydantic import model_validator
9
13
  from typing_extensions import Annotated
@@ -12,7 +16,6 @@ from typing_extensions import Self
12
16
  from ..base.content import IdInt
13
17
  from ..base.content import NameStr
14
18
  from ..base.content import SafeStr
15
- from ..base.content import id_default
16
19
  from ..base.shepherd import ShpModel
17
20
  from ..testbed.target import Target
18
21
  from ..testbed.testbed import Testbed
@@ -21,16 +24,13 @@ from .target_config import TargetConfig
21
24
 
22
25
 
23
26
  class Experiment(ShpModel, title="Config of an Experiment"):
24
- """Configuration for Experiments on the Shepherd-Testbed
25
- emulating Energy Environments for Target Nodes
26
- """
27
+ """Config for experiments on the testbed emulating energy environments for target nodes."""
27
28
 
28
29
  # General Properties
29
- id: IdInt = Field( # noqa: A003
30
- description="Unique ID",
31
- default_factory=id_default,
32
- )
30
+ # id: UUID4 ... # TODO: db-migration - temp fix for documentation
31
+ id: Union[UUID4, int] = Field(default_factory=uuid4)
33
32
  # ⤷ TODO: automatic ID is problematic for identification by hash
33
+
34
34
  name: NameStr
35
35
  description: Annotated[
36
36
  Optional[SafeStr], Field(description="Required for public instances")
@@ -38,12 +38,12 @@ class Experiment(ShpModel, title="Config of an Experiment"):
38
38
  comment: Optional[SafeStr] = None
39
39
  created: datetime = Field(default_factory=datetime.now)
40
40
 
41
- # Ownership & Access, TODO
42
- owner_id: Optional[IdInt] = 5472 # UUID?
41
+ # Ownership & Access
42
+ owner_id: Optional[IdInt] = None
43
43
 
44
44
  # feedback
45
- email_results: Optional[EmailStr] = None
46
- # ⤷ TODO: can be bool, as its linked to account
45
+ email_results: bool = False
46
+
47
47
  sys_logging: SystemLogging = SystemLogging(dmesg=True, ptp=True, shepherd=True)
48
48
 
49
49
  # schedule
@@ -90,7 +90,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
90
90
  obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
91
91
  if len(target_ids) > len(set(obs_ids)):
92
92
  raise ValueError(
93
- "Observer used more than once in Experiment -> only 1 target per observer!"
93
+ "Observer is used more than once in Experiment -> only 1 target per observer!"
94
94
  )
95
95
 
96
96
  def get_target_ids(self) -> list:
@@ -101,4 +101,5 @@ class Experiment(ShpModel, title="Config of an Experiment"):
101
101
  if target_id in _config.target_IDs:
102
102
  return _config
103
103
  # gets already caught in target_config - but keep:
104
- raise ValueError(f"Target-ID {target_id} was not found in Experiment '{self.name}'")
104
+ msg = f"Target-ID {target_id} was not found in Experiment '{self.name}'"
105
+ raise ValueError(msg)
@@ -1,3 +1,5 @@
1
+ """Configs for observer features like gpio- & power-tracing."""
2
+
1
3
  from datetime import timedelta
2
4
  from enum import Enum
3
5
  from typing import List
@@ -15,7 +17,8 @@ from ..testbed.gpio import GPIO
15
17
 
16
18
 
17
19
  class PowerTracing(ShpModel, title="Config for Power-Tracing"):
18
- """Configuration for recording the Power-Consumption of the Target Nodes
20
+ """Configuration for recording the Power-Consumption of the Target Nodes.
21
+
19
22
  TODO: postprocessing not implemented ATM
20
23
  """
21
24
 
@@ -45,12 +48,13 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
45
48
  if not self.calculate_power and discard_all:
46
49
  raise ValueError("Error in config -> tracing enabled, but output gets discarded")
47
50
  if self.calculate_power:
48
- raise ValueError("postprocessing not implemented ATM")
51
+ raise NotImplementedError("postprocessing not implemented ATM")
49
52
  return self
50
53
 
51
54
 
52
55
  class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
53
- """Configuration for recording the GPIO-Output of the Target Nodes
56
+ """Configuration for recording the GPIO-Output of the Target Nodes.
57
+
54
58
  TODO: postprocessing not implemented ATM
55
59
  """
56
60
 
@@ -66,7 +70,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
66
70
 
67
71
  # post-processing,
68
72
  uart_decode: bool = False
69
- # todo: quickfix - uart-log currently done online in userspace
73
+ # TODO: quickfix - uart-log currently done online in userspace
70
74
  # NOTE: gpio-tracing currently shows rather big - but rare - "blind" windows (~1-4us)
71
75
  uart_pin: GPIO = GPIO(name="GPIO8")
72
76
  uart_baudrate: Annotated[int, Field(ge=2_400, le=921_600)] = 115_200
@@ -84,13 +88,15 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
84
88
 
85
89
 
86
90
  class GpioLevel(str, Enum):
91
+ """Options for setting the gpio-level or state."""
92
+
87
93
  low = "L"
88
94
  high = "H"
89
95
  toggle = "X" # TODO: not the smartest decision for writing a converter
90
96
 
91
97
 
92
98
  class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
93
- """Configuration for a single GPIO-Event (Actuation)"""
99
+ """Configuration for a single GPIO-Event (Actuation)."""
94
100
 
95
101
  delay: PositiveFloat
96
102
  # ⤷ from start_time
@@ -104,7 +110,8 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
104
110
  @model_validator(mode="after")
105
111
  def post_validation(self) -> Self:
106
112
  if not self.gpio.user_controllable():
107
- raise ValueError(f"GPIO '{self.gpio.name}' in actuation-event not controllable by user")
113
+ msg = f"GPIO '{self.gpio.name}' in actuation-event not controllable by user"
114
+ raise ValueError(msg)
108
115
  return self
109
116
 
110
117
  def get_events(self) -> np.ndarray:
@@ -113,11 +120,10 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
113
120
 
114
121
 
115
122
  class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
116
- """Configuration for a GPIO-Actuation-Sequence
117
- TODO: not implemented ATM:
118
- - decide if pru control sys-gpio or
119
- - reverses pru-gpio (preferred if possible)
120
- """
123
+ """Configuration for a GPIO-Actuation-Sequence."""
124
+
125
+ # TODO: not implemented ATM - decide if pru control sys-gpio or
126
+ # TODO: not implemented ATM - reverses pru-gpio (preferred if possible)
121
127
 
122
128
  events: Annotated[List[GpioEvent], Field(min_length=1, max_length=1024)]
123
129
 
@@ -126,7 +132,7 @@ class GpioActuation(ShpModel, title="Config for GPIO-Actuation"):
126
132
 
127
133
 
128
134
  class SystemLogging(ShpModel, title="Config for System-Logging"):
129
- """Configuration for recording Debug-Output of the Observers System-Services"""
135
+ """Configuration for recording Debug-Output of the Observers System-Services."""
130
136
 
131
137
  dmesg: bool = True
132
138
  ptp: bool = True
@@ -1,3 +1,5 @@
1
+ """Configuration related to Target Nodes (DuT)."""
2
+
1
3
  from typing import List
2
4
  from typing import Optional
3
5
 
@@ -19,7 +21,7 @@ from .observer_features import PowerTracing
19
21
 
20
22
 
21
23
  class TargetConfig(ShpModel, title="Target Config"):
22
- """Configuration for Target Nodes (DuT)"""
24
+ """Configuration related to Target Nodes (DuT)."""
23
25
 
24
26
  target_IDs: Annotated[List[IdInt], Field(min_length=1, max_length=128)]
25
27
  custom_IDs: Optional[Annotated[List[IdInt16], Field(min_length=1, max_length=128)]] = None
@@ -44,7 +46,8 @@ class TargetConfig(ShpModel, title="Target Config"):
44
46
  @model_validator(mode="after")
45
47
  def post_validation(self) -> Self:
46
48
  if not self.energy_env.valid:
47
- raise ValueError(f"EnergyEnv '{self.energy_env.name}' for target must be valid")
49
+ msg = f"EnergyEnv '{self.energy_env.name}' for target must be valid"
50
+ raise ValueError(msg)
48
51
  for _id in self.target_IDs:
49
52
  target = Target(id=_id)
50
53
  for mcu_num in [1, 2]:
@@ -56,24 +59,25 @@ class TargetConfig(ShpModel, title="Target Config"):
56
59
  fw_def = Firmware(name=tgt_mcu.fw_name_default)
57
60
  # ⤷ this will raise if default is faulty
58
61
  if tgt_mcu.id != fw_def.mcu.id:
59
- raise ValueError(
62
+ msg = (
60
63
  f"Default-Firmware for MCU{mcu_num} of Target-ID '{target.id}' "
61
64
  f"(={fw_def.mcu.name}) "
62
65
  f"is incompatible (={tgt_mcu.name})"
63
66
  )
67
+ raise ValueError(msg)
64
68
  if has_fw and has_mcu and val_fw.mcu.id != tgt_mcu.id:
65
- raise ValueError(
69
+ msg = (
66
70
  f"Firmware{mcu_num} for MCU of Target-ID '{target.id}' "
67
71
  f"(={val_fw.mcu.name}) "
68
72
  f"is incompatible (={tgt_mcu.name})"
69
73
  )
74
+ raise ValueError(msg)
70
75
 
71
76
  c_ids = self.custom_IDs
72
77
  t_ids = self.target_IDs
73
78
  if c_ids is not None and (len(set(c_ids)) < len(set(t_ids))):
74
- raise ValueError(
75
- f"Provided custom IDs {c_ids} not enough to cover target range {t_ids}"
76
- )
79
+ msg = f"Provided custom IDs {c_ids} not enough to cover target range {t_ids}"
80
+ raise ValueError(msg)
77
81
  # TODO: if custom ids present, firmware must be ELF
78
82
  return self
79
83
 
@@ -0,0 +1,88 @@
1
+ ## Datastructure
2
+
3
+ ### experiment-structure
4
+
5
+ - basics
6
+ - scheduling
7
+ - **SystemLogger**
8
+ - ~~AuxPort~~
9
+ - TargetConfigs
10
+ - Programming
11
+ - PowerTracer
12
+ - GpioTracer
13
+ - GpioActuator
14
+ - vSrc
15
+ - vHarvester
16
+ - [vConverter]
17
+
18
+ ## TODO
19
+
20
+ - establish internal variables ``_var``
21
+ - descriptions to parameters (docstring on sub-models)
22
+ - @kai
23
+ - firmwares
24
+ - programmer-ports determine IC
25
+ - memory read - family-code, or write factory
26
+ - when tracing v_intermediate, also this current, or output?
27
+ - ``objcopy -O ihex input.elf output.hex``
28
+ - ``-S`` will strip useless sections
29
+ - ``-I ihex -O elf32-littlearm`` for reversal is also possible
30
+ - TODO: try to find ``objdump -t [elf_file] | grep SHEPHERD_NODE_ID``
31
+
32
+ - title in class might be rubbish
33
+
34
+ - Warn about tricky syntax
35
+ - defining sub-data-models in an experiment in python:
36
+ - experiments-default: don't mention argument in init
37
+ - trace-default: init trace with empty argument list
38
+ - disable: init with "None"
39
+ - defining experiment in yaml:
40
+ - experiment-default: don't mention it
41
+ - trace-default: NOT POSSIBLE, right?
42
+ - disable: init with "null" OR just mention parameter but keep it empty
43
+
44
+ ### add documentation after creation ⇾ avoid Field()
45
+
46
+ - these do not work
47
+
48
+ ```Python
49
+ from pydantic import Field
50
+ from shepherd_core.data_models import ShpModel
51
+
52
+ class Experiment(ShpModel, title="Config of an Experiment"):
53
+ def __init__(self): # test to add doc after creation ⇾ to avoid Field()
54
+ super().__init__()
55
+ self.Config.fields["output_path"].description = "test description"
56
+ class Config:
57
+ fields: dict[str, Field] = {}
58
+ fields["output_path"] = Field(description="test description")
59
+ ```
60
+
61
+ ### simplify init of pydantic-class
62
+
63
+ What I want: init a fixture-class with Class("name") or Class(ID) instead of Class(name="name")
64
+
65
+ What does not work:
66
+
67
+ ```Python
68
+ from pathlib import Path
69
+ from typing import Union
70
+ from pydantic import root_validator
71
+ from shepherd_core.data_models import Fixture
72
+ from shepherd_core.data_models import ShpModel
73
+
74
+ fixtures = Fixture(Path("fix.yaml"), "testbed.target")
75
+
76
+
77
+ class Target(ShpModel, title="Target Node (DuT)"):
78
+ @root_validator(pre=True)
79
+ def query_database(cls, values: Union[dict, str, int]):
80
+ values = fixtures.try_completing_model(values)
81
+ values, chain = fixtures.try_inheritance(values)
82
+ return values
83
+ ```
84
+
85
+ what might work:
86
+
87
+ - writing a custom __init__() and putting lookup() there
88
+ - wait for V2
@@ -1,3 +1,8 @@
1
+ """Module for task-related data-modules.
2
+
3
+ These models import externally from all other model-modules!
4
+ """
5
+
1
6
  from pathlib import Path
2
7
  from typing import List
3
8
  from typing import Optional
@@ -33,7 +38,9 @@ __all__ = [
33
38
 
34
39
 
35
40
  def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = None) -> Wrapper:
36
- """- Opens file (from Path or str of Path)
41
+ """Open file and extract tasks.
42
+
43
+ - Open file (from Path or str of Path)
37
44
  - wraps task-model
38
45
  - and if it's an TestbedTasks it will extract the correct ObserverTask
39
46
  """
@@ -50,7 +57,7 @@ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = N
50
57
  parameters=config.model_dump(),
51
58
  )
52
59
  else:
53
- raise ValueError("had unknown input: %s", type(config))
60
+ raise TypeError("had unknown input: %s", type(config))
54
61
 
55
62
  if shp_wrap.datatype == TestbedTasks.__name__:
56
63
  if observer is None:
@@ -72,7 +79,7 @@ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = N
72
79
 
73
80
 
74
81
  def extract_tasks(shp_wrap: Wrapper, *, no_task_sets: bool = True) -> List[ShpModel]:
75
- """ """
82
+ """Make the individual task-sets usable for each observer."""
76
83
  if shp_wrap.datatype == ObserverTasks.__name__:
77
84
  obt = ObserverTasks(**shp_wrap.parameters)
78
85
  content = obt.get_tasks()
@@ -1,3 +1,5 @@
1
+ """Configuration for the Observer in Emulation-Mode."""
2
+
1
3
  import copy
2
4
  from datetime import datetime
3
5
  from datetime import timedelta
@@ -26,10 +28,10 @@ from ..testbed.cape import TargetPort
26
28
 
27
29
 
28
30
  class Compression(str, Enum):
29
- lzf = "lzf" # not native hdf5
30
- gzip1 = 1 # higher compr & load
31
- gzip = 1
32
- default = "lzf"
31
+ """Options for choosing a dataset-compression."""
32
+
33
+ lzf = default = "lzf" # not native hdf5
34
+ gzip1 = gzip = 1 # higher compr & load
33
35
  null = None
34
36
  # NOTE: changed to lzf as BBB needs every straw it can get
35
37
 
@@ -39,7 +41,7 @@ c_translate = {"lzf": "lzf", "1": 1, "None": None, None: None}
39
41
 
40
42
 
41
43
  class EmulationTask(ShpModel):
42
- """Configuration for the Observer in Emulation-Mode"""
44
+ """Configuration for the Observer in Emulation-Mode."""
43
45
 
44
46
  # General config
45
47
  input_path: Path
@@ -111,10 +113,11 @@ class EmulationTask(ShpModel):
111
113
  has_time = self.time_start is not None
112
114
  time_now = datetime.now().astimezone()
113
115
  if has_time and self.time_start < time_now:
114
- raise ValueError(
116
+ msg = (
115
117
  "Start-Time for Emulation can't be in the past "
116
118
  f"('{self.time_start}' vs '{time_now}'."
117
119
  )
120
+ raise ValueError(msg)
118
121
  if self.duration and self.duration.total_seconds() < 0:
119
122
  raise ValueError("Task-Duration can't be negative.")
120
123
  if isinstance(self.voltage_aux, str) and self.voltage_aux not in {
@@ -1,3 +1,5 @@
1
+ """Config for Task that adds the custom ID to the firmware & stores it into a file."""
2
+
1
3
  import copy
2
4
  from pathlib import Path
3
5
  from typing import Optional
@@ -24,7 +26,7 @@ from ..testbed.target import MCUPort
24
26
 
25
27
 
26
28
  class FirmwareModTask(ShpModel):
27
- """Config for Task that adds the custom ID to the firmware & stores it into a file"""
29
+ """Config for Task that adds the custom ID to the firmware & stores it into a file."""
28
30
 
29
31
  data: Union[FirmwareStr, Path]
30
32
  data_type: FirmwareDType
@@ -79,7 +81,7 @@ class FirmwareModTask(ShpModel):
79
81
  **kwargs: Unpack[TypedDict],
80
82
  ) -> Self:
81
83
  if not isinstance(fw, Firmware):
82
- raise ValueError("fw-argument must be of type Firmware")
84
+ raise TypeError("fw-argument must be of type Firmware")
83
85
  kwargs["data"] = fw.data
84
86
  kwargs["data_type"] = fw.data_type
85
87
  fw.compare_hash()
@@ -1,3 +1,5 @@
1
+ """Config for the Observer in Harvest-Mode to record IV data from a harvesting-source."""
2
+
1
3
  from datetime import datetime
2
4
  from datetime import timedelta
3
5
  from pathlib import Path
@@ -17,9 +19,7 @@ from .emulation import Compression
17
19
 
18
20
 
19
21
  class HarvestTask(ShpModel):
20
- """Configuration for the Observer in Harvest-Mode
21
- Record IV data from a harvest-source
22
- """
22
+ """Config for the Observer in Harvest-Mode to record IV data from a harvesting-source."""
23
23
 
24
24
  # General config
25
25
  output_path: Path
@@ -73,10 +73,11 @@ class HarvestTask(ShpModel):
73
73
  has_time = self.time_start is not None
74
74
  time_now = datetime.now().astimezone()
75
75
  if has_time and self.time_start < time_now:
76
- raise ValueError(
76
+ msg = (
77
77
  "Start-Time for Emulation can't be in the past "
78
78
  f"('{self.time_start}' vs '{time_now}'."
79
79
  )
80
+ raise ValueError(msg)
80
81
  if self.duration and self.duration.total_seconds() < 0:
81
82
  raise ValueError("Task-Duration can't be negative.")
82
83
  return self
@@ -1,3 +1,5 @@
1
+ """Collection of tasks for selected observer included in experiment."""
2
+
1
3
  from datetime import datetime
2
4
  from datetime import timedelta
3
5
  from pathlib import Path
@@ -18,10 +20,10 @@ from .programming import ProgrammingTask
18
20
 
19
21
 
20
22
  class ObserverTasks(ShpModel):
21
- """Collection of tasks for selected observer included in experiment"""
23
+ """Collection of tasks for selected observer included in experiment."""
22
24
 
23
25
  observer: NameStr
24
- owner_id: IdInt
26
+ owner_id: Optional[IdInt] # TODO: set to optional for now, shouldn't be
25
27
 
26
28
  # PRE PROCESS
27
29
  time_prep: datetime # TODO: should be optional
@@ -1,3 +1,5 @@
1
+ """Config for a Task programming the selected target."""
2
+
1
3
  import copy
2
4
  from pathlib import Path
3
5
  from typing import Optional
@@ -21,7 +23,7 @@ from ..testbed.testbed import Testbed
21
23
 
22
24
 
23
25
  class ProgrammingTask(ShpModel):
24
- """Config for Task programming the target selected"""
26
+ """Config for a Task programming the selected target."""
25
27
 
26
28
  firmware_file: Path
27
29
  target_port: TargetPort = TargetPort.A
@@ -1,12 +1,14 @@
1
+ """Collection of tasks for all observers included in experiment."""
2
+
1
3
  from typing import List
2
4
  from typing import Optional
3
5
 
4
- from pydantic import EmailStr
5
6
  from pydantic import Field
6
7
  from pydantic import validate_call
7
8
  from typing_extensions import Annotated
8
9
  from typing_extensions import Self
9
10
 
11
+ from ..base.content import IdInt
10
12
  from ..base.content import NameStr
11
13
  from ..base.shepherd import ShpModel
12
14
  from ..experiment.experiment import Experiment
@@ -15,13 +17,16 @@ from .observer_tasks import ObserverTasks
15
17
 
16
18
 
17
19
  class TestbedTasks(ShpModel):
18
- """Collection of tasks for all observers included in experiment"""
20
+ """Collection of tasks for all observers included in experiment."""
19
21
 
20
22
  name: NameStr
21
23
  observer_tasks: Annotated[List[ObserverTasks], Field(min_length=1, max_length=128)]
22
24
 
23
25
  # POST PROCESS
24
- email: Optional[EmailStr] = None
26
+ email_results: bool = False
27
+ owner_id: Optional[IdInt]
28
+ # TODO: had real email previously, does it really need these at all?
29
+ # DB stores experiment and knows when to email
25
30
 
26
31
  @classmethod
27
32
  @validate_call
@@ -34,7 +39,8 @@ class TestbedTasks(ShpModel):
34
39
  return cls(
35
40
  name=xp.name,
36
41
  observer_tasks=obs_tasks,
37
- email=xp.email_results,
42
+ email_results=xp.email_results,
43
+ owner_id=xp.owner_id,
38
44
  )
39
45
 
40
46
  def get_observer_tasks(self, observer: str) -> Optional[ObserverTasks]:
@@ -1,3 +1,8 @@
1
+ """Module for testbed-related data-models.
2
+
3
+ These models import externally from: /base
4
+ """
5
+
1
6
  from .cape import Cape
2
7
  from .cape import TargetPort
3
8
  from .gpio import GPIO
@@ -10,8 +15,6 @@ from .target import IdInt16
10
15
  from .target import Target
11
16
  from .testbed import Testbed
12
17
 
13
- # these models import externally from: /base
14
-
15
18
  __all__ = [
16
19
  "Testbed",
17
20
  "Observer",
@@ -1,3 +1,5 @@
1
+ """meta-data representation of a testbed-component (physical object)."""
2
+
1
3
  from datetime import date
2
4
  from datetime import datetime
3
5
  from enum import Enum
@@ -15,16 +17,16 @@ from ..base.shepherd import ShpModel
15
17
 
16
18
 
17
19
  class TargetPort(str, Enum):
18
- A = "A"
19
- B = "B"
20
- a = "A"
21
- b = "B"
20
+ """Options for choosing a target-port."""
21
+
22
+ A = a = "A"
23
+ B = b = "B"
22
24
 
23
25
 
24
26
  class Cape(ShpModel, title="Shepherd-Cape"):
25
- """meta-data representation of a testbed-component (physical object)"""
27
+ """meta-data representation of a testbed-component (physical object)."""
26
28
 
27
- id: IdInt # noqa: A003
29
+ id: IdInt
28
30
  name: NameStr
29
31
  version: NameStr
30
32
  description: SafeStr
@@ -1,3 +1,5 @@
1
+ """meta-data representation of a testbed-component (physical object)."""
2
+
1
3
  from enum import Enum
2
4
  from typing import Optional
3
5
 
@@ -15,18 +17,17 @@ from ..base.shepherd import ShpModel
15
17
 
16
18
 
17
19
  class Direction(str, Enum):
18
- Input = "IN"
19
- IN = "IN"
20
- Output = "OUT"
21
- OUT = "OUT"
22
- Bidirectional = "IO"
23
- IO = "IO"
20
+ """Options for pin-direction."""
21
+
22
+ Input = IN = "IN"
23
+ Output = OUT = "OUT"
24
+ Bidirectional = IO = "IO"
24
25
 
25
26
 
26
27
  class GPIO(ShpModel, title="GPIO of Observer Node"):
27
- """meta-data representation of a testbed-component"""
28
+ """meta-data representation of a testbed-component."""
28
29
 
29
- id: IdInt # noqa: A003
30
+ id: IdInt
30
31
  name: NameStr
31
32
  description: Optional[SafeStr] = None
32
33
  comment: Optional[SafeStr] = None
@@ -54,10 +55,11 @@ class GPIO(ShpModel, title="GPIO of Observer Node"):
54
55
  no_pru = (self.reg_pru is None) or (self.pin_pru is None)
55
56
  no_sys = (self.reg_sys is None) or (self.pin_sys is None)
56
57
  if no_pru and no_sys:
57
- raise ValueError(
58
+ msg = (
58
59
  "GPIO-Instance is faulty -> "
59
60
  f"it needs to use pru or sys, content: {self.model_dump()}"
60
61
  )
62
+ raise ValueError(msg)
61
63
  return self
62
64
 
63
65
  def user_controllable(self) -> bool:
@@ -1,3 +1,5 @@
1
+ """meta-data representation of a testbed-component (physical object)."""
2
+
1
3
  from enum import Enum
2
4
  from typing import Optional
3
5
 
@@ -13,20 +15,18 @@ from ..base.shepherd import ShpModel
13
15
 
14
16
 
15
17
  class ProgrammerProtocol(str, Enum):
16
- SWD = "SWD"
17
- swd = "SWD"
18
- SBW = "SBW"
19
- sbw = "SBW"
20
- JTAG = "JTAG"
21
- jtag = "JTAG"
22
- UART = "UART"
23
- uart = "UART"
18
+ """Options regarding the programming-protocol."""
19
+
20
+ SWD = swd = "SWD"
21
+ SBW = sbw = "SBW"
22
+ JTAG = jtag = "JTAG"
23
+ UART = uart = "UART"
24
24
 
25
25
 
26
26
  class MCU(ShpModel, title="Microcontroller of the Target Node"):
27
- """meta-data representation of a testbed-component (physical object)"""
27
+ """meta-data representation of a testbed-component (physical object)."""
28
28
 
29
- id: IdInt # noqa: A003
29
+ id: IdInt
30
30
  name: NameStr
31
31
  description: SafeStr
32
32
  comment: Optional[SafeStr] = None