shepherd-core 2023.11.1__py3-none-any.whl → 2023.12.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.
Files changed (42) hide show
  1. shepherd_core/__init__.py +1 -1
  2. shepherd_core/data_models/base/content.py +3 -8
  3. shepherd_core/data_models/base/shepherd.py +2 -6
  4. shepherd_core/data_models/content/virtual_source.py +13 -40
  5. shepherd_core/data_models/experiment/experiment.py +3 -8
  6. shepherd_core/data_models/experiment/observer_features.py +4 -9
  7. shepherd_core/data_models/experiment/target_config.py +4 -11
  8. shepherd_core/data_models/task/__init__.py +2 -6
  9. shepherd_core/data_models/task/emulation.py +7 -14
  10. shepherd_core/data_models/task/firmware_mod.py +1 -3
  11. shepherd_core/data_models/task/harvest.py +1 -3
  12. shepherd_core/data_models/testbed/observer.py +6 -22
  13. shepherd_core/data_models/testbed/testbed.py +1 -3
  14. shepherd_core/decoder_waveform/uart.py +8 -26
  15. shepherd_core/fw_tools/__init__.py +1 -3
  16. shepherd_core/fw_tools/converter.py +4 -12
  17. shepherd_core/fw_tools/patcher.py +4 -12
  18. shepherd_core/inventory/__init__.py +3 -9
  19. shepherd_core/inventory/system.py +2 -5
  20. shepherd_core/logger.py +1 -3
  21. shepherd_core/reader.py +26 -41
  22. shepherd_core/testbed_client/client.py +5 -16
  23. shepherd_core/testbed_client/fixtures.py +4 -14
  24. shepherd_core/testbed_client/user_model.py +1 -3
  25. shepherd_core/vsource/virtual_converter_model.py +4 -14
  26. shepherd_core/vsource/virtual_harvester_model.py +1 -3
  27. shepherd_core/vsource/virtual_source_model.py +2 -6
  28. shepherd_core/writer.py +11 -22
  29. {shepherd_core-2023.11.1.dist-info → shepherd_core-2023.12.1.dist-info}/METADATA +2 -5
  30. {shepherd_core-2023.11.1.dist-info → shepherd_core-2023.12.1.dist-info}/RECORD +42 -42
  31. {shepherd_core-2023.11.1.dist-info → shepherd_core-2023.12.1.dist-info}/WHEEL +1 -1
  32. tests/data_models/test_content_models.py +3 -9
  33. tests/data_models/test_experiment_models.py +2 -4
  34. tests/data_models/test_task_generation.py +1 -1
  35. tests/inventory/test_inventory.py +1 -3
  36. tests/test_cal_hw.py +2 -6
  37. tests/test_writer.py +2 -2
  38. tests/vsource/conftest.py +1 -3
  39. tests/vsource/test_converter.py +2 -6
  40. tests/vsource/test_harvester.py +3 -9
  41. {shepherd_core-2023.11.1.dist-info → shepherd_core-2023.12.1.dist-info}/top_level.txt +0 -0
  42. {shepherd_core-2023.11.1.dist-info → shepherd_core-2023.12.1.dist-info}/zip-safe +0 -0
shepherd_core/__init__.py CHANGED
@@ -22,7 +22,7 @@ from .testbed_client.client import TestbedClient
22
22
  from .testbed_client.client import tb_client
23
23
  from .writer import Writer
24
24
 
25
- __version__ = "2023.11.1"
25
+ __version__ = "2023.12.1"
26
26
 
27
27
  __all__ = [
28
28
  "Reader",
@@ -14,9 +14,7 @@ from .timezone import local_now
14
14
  # constr -> to_lower=True, max_length=16, regex=r"^[\w]+$"
15
15
  # ⤷ Regex = AlphaNum
16
16
  IdInt = Annotated[int, Field(ge=0, lt=2**128)]
17
- NameStr = Annotated[
18
- str, StringConstraints(max_length=32, pattern=r'^[^<>:;,?"*|\/\\]+$')
19
- ]
17
+ NameStr = Annotated[str, StringConstraints(max_length=32, pattern=r'^[^<>:;,?"*|\/\\]+$')]
20
18
  # ⤷ Regex = FileSystem-Compatible ASCII
21
19
  SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
22
20
  # ⤷ Regex = All Printable ASCII-Characters with Space
@@ -35,9 +33,7 @@ class ContentModel(ShpModel):
35
33
  default_factory=id_default,
36
34
  )
37
35
  name: NameStr
38
- description: Annotated[
39
- Optional[SafeStr], Field(description="Required when public")
40
- ] = None
36
+ description: Annotated[Optional[SafeStr], Field(description="Required when public")] = None
41
37
  comment: Optional[SafeStr] = None
42
38
  created: datetime = Field(default_factory=datetime.now)
43
39
 
@@ -57,7 +53,6 @@ class ContentModel(ShpModel):
57
53
  is_visible = self.visible2group or self.visible2all
58
54
  if is_visible and self.description is None:
59
55
  raise ValueError(
60
- "Public instances require a description "
61
- "(check visible2*- and description-field)"
56
+ "Public instances require a description (check visible2*- and description-field)"
62
57
  )
63
58
  return self
@@ -27,9 +27,7 @@ def path2str(
27
27
 
28
28
 
29
29
  def time2int(dumper: Dumper, data: timedelta) -> ScalarNode:
30
- return dumper.represent_scalar(
31
- "tag:yaml.org,2002:int", str(int(data.total_seconds()))
32
- )
30
+ return dumper.represent_scalar("tag:yaml.org,2002:int", str(int(data.total_seconds())))
33
31
 
34
32
 
35
33
  def generic2str(dumper: Dumper, data: Any) -> ScalarNode:
@@ -109,9 +107,7 @@ class ShpModel(BaseModel):
109
107
  def schema_to_file(cls, path: Union[str, Path]) -> None:
110
108
  """Store schema to yaml (for frontend-generators)"""
111
109
  model_dict = cls.model_json_schema()
112
- model_yaml = yaml.safe_dump(
113
- model_dict, default_flow_style=False, sort_keys=False
114
- )
110
+ model_yaml = yaml.safe_dump(model_dict, default_flow_style=False, sort_keys=False)
115
111
  with Path(path).resolve().with_suffix(".yaml").open("w") as f:
116
112
  f.write(model_yaml)
117
113
 
@@ -153,15 +153,9 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
153
153
  dV_output_imed_low_mV = 0
154
154
 
155
155
  # protect from complex solutions (non valid input combinations)
156
- if not (
157
- isinstance(dV_output_en_thrs_mV, (int, float))
158
- and (dV_output_en_thrs_mV >= 0)
159
- ):
156
+ if not (isinstance(dV_output_en_thrs_mV, (int, float)) and (dV_output_en_thrs_mV >= 0)):
160
157
  dV_output_en_thrs_mV = 0
161
- if not (
162
- isinstance(dV_output_imed_low_mV, (int, float))
163
- and (dV_output_imed_low_mV >= 0)
164
- ):
158
+ if not (isinstance(dV_output_imed_low_mV, (int, float)) and (dV_output_imed_low_mV >= 0)):
165
159
  logger.warning("VSrc: C_output shouldn't be larger than C_intermediate")
166
160
  dV_output_imed_low_mV = 0
167
161
 
@@ -171,9 +165,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
171
165
 
172
166
  if self.V_intermediate_enable_threshold_mV > V_pre_output_mV:
173
167
  values["dV_enable_output_mV"] = dV_output_en_thrs_mV
174
- values[
175
- "V_enable_output_threshold_mV"
176
- ] = self.V_intermediate_enable_threshold_mV
168
+ values["V_enable_output_threshold_mV"] = self.V_intermediate_enable_threshold_mV
177
169
 
178
170
  else:
179
171
  values["dV_enable_output_mV"] = dV_output_imed_low_mV
@@ -182,20 +174,14 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
182
174
  )
183
175
 
184
176
  if self.V_intermediate_disable_threshold_mV > V_pre_output_mV:
185
- values[
186
- "V_disable_output_threshold_mV"
187
- ] = self.V_intermediate_disable_threshold_mV
177
+ values["V_disable_output_threshold_mV"] = self.V_intermediate_disable_threshold_mV
188
178
  else:
189
179
  values["V_disable_output_threshold_mV"] = V_pre_output_mV
190
180
 
191
181
  else:
192
182
  values["dV_enable_output_mV"] = dV_output_en_thrs_mV
193
- values[
194
- "V_enable_output_threshold_mV"
195
- ] = self.V_intermediate_enable_threshold_mV
196
- values[
197
- "V_disable_output_threshold_mV"
198
- ] = self.V_intermediate_disable_threshold_mV
183
+ values["V_enable_output_threshold_mV"] = self.V_intermediate_enable_threshold_mV
184
+ values["V_disable_output_threshold_mV"] = self.V_intermediate_disable_threshold_mV
199
185
  return values
200
186
 
201
187
  def calc_converter_mode(self, *, log_intermediate_node: bool) -> int:
@@ -286,9 +272,7 @@ class ConverterPRUConfig(ShpModel):
286
272
  states = data.calc_internal_states()
287
273
  return cls(
288
274
  # General
289
- converter_mode=data.calc_converter_mode(
290
- log_intermediate_node=log_intermediate_node
291
- ),
275
+ converter_mode=data.calc_converter_mode(log_intermediate_node=log_intermediate_node),
292
276
  interval_startup_delay_drain_n=round(
293
277
  data.interval_startup_delay_drain_ms * samplerate_sps_default * 1e-3
294
278
  ),
@@ -299,26 +283,16 @@ class ConverterPRUConfig(ShpModel):
299
283
  Constant_us_per_nF_n28=data.calc_cap_constant_us_per_nF_n28(),
300
284
  V_intermediate_init_uV=round(data.V_intermediate_init_mV * 1e3),
301
285
  I_intermediate_leak_nA=round(data.I_intermediate_leak_nA),
302
- V_enable_output_threshold_uV=round(
303
- states["V_enable_output_threshold_mV"] * 1e3
304
- ),
305
- V_disable_output_threshold_uV=round(
306
- states["V_disable_output_threshold_mV"] * 1e3
307
- ),
286
+ V_enable_output_threshold_uV=round(states["V_enable_output_threshold_mV"] * 1e3),
287
+ V_disable_output_threshold_uV=round(states["V_disable_output_threshold_mV"] * 1e3),
308
288
  dV_enable_output_uV=round(states["dV_enable_output_mV"] * 1e3),
309
289
  interval_check_thresholds_n=round(
310
290
  data.interval_check_thresholds_ms * samplerate_sps_default * 1e-3
311
291
  ),
312
- V_pwr_good_enable_threshold_uV=round(
313
- data.V_pwr_good_enable_threshold_mV * 1e3
314
- ),
315
- V_pwr_good_disable_threshold_uV=round(
316
- data.V_pwr_good_disable_threshold_mV * 1e3
317
- ),
292
+ V_pwr_good_enable_threshold_uV=round(data.V_pwr_good_enable_threshold_mV * 1e3),
293
+ V_pwr_good_disable_threshold_uV=round(data.V_pwr_good_disable_threshold_mV * 1e3),
318
294
  immediate_pwr_good_signal=data.immediate_pwr_good_signal,
319
- V_output_log_gpio_threshold_uV=round(
320
- data.V_output_log_gpio_threshold_mV * 1e3
321
- ),
295
+ V_output_log_gpio_threshold_uV=round(data.V_output_log_gpio_threshold_mV * 1e3),
322
296
  # Boost-Converter
323
297
  V_input_boost_threshold_uV=round(data.V_input_boost_threshold_mV * 1e3),
324
298
  V_intermediate_max_uV=round(data.V_intermediate_max_mV * 1e3),
@@ -330,8 +304,7 @@ class ConverterPRUConfig(ShpModel):
330
304
  LUT_input_I_min_log2_nA=data.LUT_input_I_min_log2_nA,
331
305
  LUT_output_I_min_log2_nA=data.LUT_output_I_min_log2_nA,
332
306
  LUT_inp_efficiency_n8=[
333
- [min(255, round(256 * ival)) for ival in il]
334
- for il in data.LUT_input_efficiency
307
+ [min(255, round(256 * ival)) for ival in il] for il in data.LUT_input_efficiency
335
308
  ],
336
309
  LUT_out_inv_efficiency_n4=[
337
310
  min((2**14), round((2**4) / value)) if (value > 0) else int(2**14)
@@ -80,9 +80,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
80
80
  if len(target_ids) > len(set(target_ids)):
81
81
  raise ValueError("Target-ID used more than once in Experiment!")
82
82
  if len(target_ids) > len(set(custom_ids)):
83
- raise ValueError(
84
- "Custom Target-ID are faulty (some form of id-collisions)!"
85
- )
83
+ raise ValueError("Custom Target-ID are faulty (some form of id-collisions)!")
86
84
 
87
85
  @staticmethod
88
86
  def validate_observers(configs: List[TargetConfig]) -> None:
@@ -92,8 +90,7 @@ class Experiment(ShpModel, title="Config of an Experiment"):
92
90
  obs_ids = [testbed.get_observer(_id).id for _id in target_ids]
93
91
  if len(target_ids) > len(set(obs_ids)):
94
92
  raise ValueError(
95
- "Observer used more than once in Experiment "
96
- "-> only 1 target per observer!"
93
+ "Observer used more than once in Experiment -> only 1 target per observer!"
97
94
  )
98
95
 
99
96
  def get_target_ids(self) -> list:
@@ -104,6 +101,4 @@ class Experiment(ShpModel, title="Config of an Experiment"):
104
101
  if target_id in _config.target_IDs:
105
102
  return _config
106
103
  # gets already caught in target_config - but keep:
107
- raise ValueError(
108
- f"Target-ID {target_id} was not found in Experiment '{self.name}'"
109
- )
104
+ raise ValueError(f"Target-ID {target_id} was not found in Experiment '{self.name}'")
@@ -43,9 +43,7 @@ class PowerTracing(ShpModel, title="Config for Power-Tracing"):
43
43
 
44
44
  discard_all = self.discard_current and self.discard_voltage
45
45
  if not self.calculate_power and discard_all:
46
- raise ValueError(
47
- "Error in config -> tracing enabled, but output gets discarded"
48
- )
46
+ raise ValueError("Error in config -> tracing enabled, but output gets discarded")
49
47
  if self.calculate_power:
50
48
  raise ValueError("postprocessing not implemented ATM")
51
49
  return self
@@ -59,9 +57,7 @@ class GpioTracing(ShpModel, title="Config for GPIO-Tracing"):
59
57
  # initial recording
60
58
  mask: Annotated[int, Field(ge=0, lt=2**10)] = 0b11_1111_1111 # all
61
59
  # ⤷ TODO: custom mask not implemented in PRU, ATM
62
- gpios: Optional[
63
- Annotated[List[GPIO], Field(min_length=1, max_length=10)]
64
- ] = None # = all
60
+ gpios: Optional[Annotated[List[GPIO], Field(min_length=1, max_length=10)]] = None # = all
65
61
  # ⤷ TODO: list of GPIO to build mask, one of both should be internal / computed field
66
62
 
67
63
  # time
@@ -108,9 +104,7 @@ class GpioEvent(ShpModel, title="Config for a GPIO-Event"):
108
104
  @model_validator(mode="after")
109
105
  def post_validation(self) -> Self:
110
106
  if not self.gpio.user_controllable():
111
- raise ValueError(
112
- f"GPIO '{self.gpio.name}' in actuation-event not controllable by user"
113
- )
107
+ raise ValueError(f"GPIO '{self.gpio.name}' in actuation-event not controllable by user")
114
108
  return self
115
109
 
116
110
  def get_events(self) -> np.ndarray:
@@ -138,6 +132,7 @@ class SystemLogging(ShpModel, title="Config for System-Logging"):
138
132
  ptp: bool = True
139
133
  shepherd: bool = True
140
134
  # TODO: rename to kernel, timesync, sheep
135
+ # TODO: add utilization as option
141
136
 
142
137
 
143
138
  # TODO: some more interaction would be good
@@ -22,18 +22,14 @@ class TargetConfig(ShpModel, title="Target Config"):
22
22
  """Configuration for Target Nodes (DuT)"""
23
23
 
24
24
  target_IDs: Annotated[List[IdInt], Field(min_length=1, max_length=128)]
25
- custom_IDs: Optional[
26
- Annotated[List[IdInt16], Field(min_length=1, max_length=128)]
27
- ] = None
25
+ custom_IDs: Optional[Annotated[List[IdInt16], Field(min_length=1, max_length=128)]] = None
28
26
  # ⤷ will replace 'const uint16_t SHEPHERD_NODE_ID' in firmware
29
27
  # if no custom ID is provided, the original ID of target is used
30
28
 
31
29
  energy_env: EnergyEnvironment # alias: input
32
30
  virtual_source: VirtualSourceConfig = VirtualSourceConfig(name="neutral")
33
31
  target_delays: Optional[
34
- Annotated[
35
- List[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)
36
- ]
32
+ Annotated[List[Annotated[int, Field(ge=0)]], Field(min_length=1, max_length=128)]
37
33
  ] = None
38
34
  # ⤷ individual starting times -> allows to use the same environment
39
35
  # TODO: delays not used ATM
@@ -48,9 +44,7 @@ class TargetConfig(ShpModel, title="Target Config"):
48
44
  @model_validator(mode="after")
49
45
  def post_validation(self) -> Self:
50
46
  if not self.energy_env.valid:
51
- raise ValueError(
52
- f"EnergyEnv '{self.energy_env.name}' for target must be valid"
53
- )
47
+ raise ValueError(f"EnergyEnv '{self.energy_env.name}' for target must be valid")
54
48
  for _id in self.target_IDs:
55
49
  target = Target(id=_id)
56
50
  for mcu_num in [1, 2]:
@@ -78,8 +72,7 @@ class TargetConfig(ShpModel, title="Target Config"):
78
72
  t_ids = self.target_IDs
79
73
  if c_ids is not None and (len(set(c_ids)) < len(set(t_ids))):
80
74
  raise ValueError(
81
- f"Provided custom IDs {c_ids} not enough "
82
- f"to cover target range {t_ids}"
75
+ f"Provided custom IDs {c_ids} not enough to cover target range {t_ids}"
83
76
  )
84
77
  # TODO: if custom ids present, firmware must be ELF
85
78
  return self
@@ -32,9 +32,7 @@ __all__ = [
32
32
  ]
33
33
 
34
34
 
35
- def prepare_task(
36
- config: Union[ShpModel, Path, str], observer: Optional[str] = None
37
- ) -> Wrapper:
35
+ def prepare_task(config: Union[ShpModel, Path, str], observer: Optional[str] = None) -> Wrapper:
38
36
  """- Opens file (from Path or str of Path)
39
37
  - wraps task-model
40
38
  - and if it's an TestbedTasks it will extract the correct ObserverTask
@@ -88,9 +86,7 @@ def extract_tasks(shp_wrap: Wrapper, *, no_task_sets: bool = True) -> List[ShpMo
88
86
  content = [ProgrammingTask(**shp_wrap.parameters)]
89
87
  elif shp_wrap.datatype == TestbedTasks.__name__:
90
88
  if no_task_sets:
91
- raise ValueError(
92
- "Model in Wrapper was TestbedTasks -> Task-Sets not allowed!"
93
- )
89
+ raise ValueError("Model in Wrapper was TestbedTasks -> Task-Sets not allowed!")
94
90
  content = [TestbedTasks(**shp_wrap.parameters)]
95
91
  else:
96
92
  raise ValueError("Extractor had unknown task: %s", shp_wrap.datatype)
@@ -29,8 +29,9 @@ class Compression(str, Enum):
29
29
  lzf = "lzf" # not native hdf5
30
30
  gzip1 = 1 # higher compr & load
31
31
  gzip = 1
32
- default = 1
32
+ default = "lzf"
33
33
  null = None
34
+ # NOTE: changed to lzf as BBB needs every straw it can get
34
35
 
35
36
 
36
37
  compressions_allowed: list = [None, "lzf", 1]
@@ -52,8 +53,7 @@ class EmulationTask(ShpModel):
52
53
  force_overwrite: bool = False
53
54
  # ⤷ Overwrite existing file
54
55
  output_compression: Optional[Compression] = Compression.default
55
- # ⤷ should be 1 (level 1 gzip), lzf, or None (order of recommendation)
56
-
56
+ # ⤷ should be lzf, 1 (gzip level 1) or None (order of recommendation)
57
57
  time_start: Optional[datetime] = None
58
58
  # timestamp or unix epoch time, None = ASAP
59
59
  duration: Optional[timedelta] = None
@@ -98,9 +98,7 @@ class EmulationTask(ShpModel):
98
98
  # convert & add local timezone-data
99
99
  has_time = values.get("time_start") is not None
100
100
  if has_time and isinstance(values["time_start"], (int, float)):
101
- values["time_start"] = datetime.fromtimestamp(
102
- values["time_start"], tz=local_tz()
103
- )
101
+ values["time_start"] = datetime.fromtimestamp(values["time_start"], tz=local_tz())
104
102
  if has_time and isinstance(values["time_start"], str):
105
103
  values["time_start"] = datetime.fromisoformat(values["time_start"])
106
104
  if has_time and values["time_start"].tzinfo is None:
@@ -123,18 +121,14 @@ class EmulationTask(ShpModel):
123
121
  "main",
124
122
  "buffer",
125
123
  }:
126
- raise ValueError(
127
- "Voltage Aux must be in float (0 - 4.5) or string 'main' / 'mid'."
128
- )
124
+ raise ValueError("Voltage Aux must be in float (0 - 4.5) or string 'main' / 'mid'.")
129
125
  if self.gpio_actuation is not None:
130
126
  raise ValueError("GPIO Actuation not yet implemented!")
131
127
  return self
132
128
 
133
129
  @classmethod
134
130
  @validate_call
135
- def from_xp(
136
- cls, xp: Experiment, tb: Testbed, tgt_id: IdInt, root_path: Path
137
- ) -> Self:
131
+ def from_xp(cls, xp: Experiment, tb: Testbed, tgt_id: IdInt, root_path: Path) -> Self:
138
132
  obs = tb.get_observer(tgt_id)
139
133
  tgt_cfg = xp.get_target_config(tgt_id)
140
134
 
@@ -144,8 +138,7 @@ class EmulationTask(ShpModel):
144
138
  time_start=copy.copy(xp.time_start),
145
139
  duration=xp.duration,
146
140
  abort_on_error=xp.abort_on_error,
147
- enable_io=(tgt_cfg.gpio_tracing is not None)
148
- or (tgt_cfg.gpio_actuation is not None),
141
+ enable_io=(tgt_cfg.gpio_tracing is not None) or (tgt_cfg.gpio_actuation is not None),
149
142
  io_port=obs.get_target_port(tgt_id),
150
143
  pwr_port=obs.get_target_port(tgt_id),
151
144
  virtual_source=tgt_cfg.virtual_source,
@@ -40,9 +40,7 @@ class FirmwareModTask(ShpModel):
40
40
  FirmwareDType.base64_hex,
41
41
  FirmwareDType.path_hex,
42
42
  }:
43
- logger.warning(
44
- "Firmware is scheduled to get custom-ID but is not in elf-format"
45
- )
43
+ logger.warning("Firmware is scheduled to get custom-ID but is not in elf-format")
46
44
  return self
47
45
 
48
46
  @classmethod
@@ -60,9 +60,7 @@ class HarvestTask(ShpModel):
60
60
  # convert & add local timezone-data, TODO: used twice, refactor
61
61
  has_time = values.get("time_start") is not None
62
62
  if has_time and isinstance(values["time_start"], (int, float)):
63
- values["time_start"] = datetime.fromtimestamp(
64
- values["time_start"], tz=local_tz()
65
- )
63
+ values["time_start"] = datetime.fromtimestamp(values["time_start"], tz=local_tz())
66
64
  if has_time and isinstance(values["time_start"], str):
67
65
  values["time_start"] = datetime.fromisoformat(values["time_start"])
68
66
  if has_time and values["time_start"].tzinfo is None:
@@ -19,9 +19,7 @@ from .target import Target
19
19
 
20
20
  MACStr = Annotated[
21
21
  str,
22
- StringConstraints(
23
- max_length=17, pattern=r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"
24
- ),
22
+ StringConstraints(max_length=17, pattern=r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"),
25
23
  ]
26
24
 
27
25
 
@@ -65,23 +63,13 @@ class Observer(ShpModel, title="Shepherd-Sheep"):
65
63
  has_cape = self.cape is not None
66
64
  has_target = (self.target_a is not None) or (self.target_b is not None)
67
65
  if not has_cape and has_target:
68
- raise ValueError(
69
- f"Observer '{self.name}' is faulty " f"-> has targets but no cape"
70
- )
66
+ raise ValueError(f"Observer '{self.name}' is faulty -> has targets but no cape")
71
67
  return self
72
68
 
73
69
  def has_target(self, target_id: int) -> bool:
74
- if (
75
- self.target_a is not None
76
- and target_id == self.target_a.id
77
- and self.target_a.active
78
- ):
70
+ if self.target_a is not None and target_id == self.target_a.id and self.target_a.active:
79
71
  return True
80
- if (
81
- self.target_b is not None
82
- and target_id == self.target_b.id
83
- and self.target_b.active
84
- ):
72
+ if self.target_b is not None and target_id == self.target_b.id and self.target_b.active:
85
73
  return True
86
74
  return False
87
75
 
@@ -91,9 +79,7 @@ class Observer(ShpModel, title="Shepherd-Sheep"):
91
79
  return TargetPort.A
92
80
  if self.target_b is not None and target_id == self.target_b.id:
93
81
  return TargetPort.B
94
- raise ValueError(
95
- f"Target-ID {target_id} was not found in Observer '{self.name}'"
96
- )
82
+ raise ValueError(f"Target-ID {target_id} was not found in Observer '{self.name}'")
97
83
 
98
84
  def get_target(self, target_id: int) -> Target:
99
85
  if self.has_target(target_id):
@@ -101,6 +87,4 @@ class Observer(ShpModel, title="Shepherd-Sheep"):
101
87
  return self.target_a
102
88
  if self.target_b is not None and target_id == self.target_b.id:
103
89
  return self.target_b
104
- raise ValueError(
105
- f"Target-ID {target_id} was not found in Observer '{self.name}'"
106
- )
90
+ raise ValueError(f"Target-ID {target_id} was not found in Observer '{self.name}'")
@@ -87,6 +87,4 @@ class Testbed(ShpModel):
87
87
  continue
88
88
  if _observer.has_target(target_id):
89
89
  return _observer
90
- raise ValueError(
91
- f"Target-ID {target_id} was not found in Testbed '{self.name}'"
92
- )
90
+ raise ValueError(f"Target-ID {target_id} was not found in Testbed '{self.name}'")
@@ -56,9 +56,7 @@ class Uart:
56
56
  (some detectors still missing)
57
57
  """
58
58
  if isinstance(content, Path):
59
- self.events_sig: np.ndarray = np.loadtxt(
60
- content.as_posix(), delimiter=",", skiprows=1
61
- )
59
+ self.events_sig: np.ndarray = np.loadtxt(content.as_posix(), delimiter=",", skiprows=1)
62
60
  # TODO: if float fails load as str -
63
61
  # cast first col as np.datetime64 with ns-resolution, convert to delta
64
62
  else:
@@ -66,9 +64,7 @@ class Uart:
66
64
 
67
65
  # verify table
68
66
  if self.events_sig.shape[1] != 2:
69
- raise TypeError(
70
- "Input file should have 2 rows -> (comma-separated) timestamp & value"
71
- )
67
+ raise TypeError("Input file should have 2 rows -> (comma-separated) timestamp & value")
72
68
  if self.events_sig.shape[0] < 8:
73
69
  raise TypeError("Input file is too short (< state-changes)")
74
70
  # verify timestamps
@@ -80,23 +76,17 @@ class Uart:
80
76
  self._convert_analog2digital()
81
77
  self._filter_redundant_states()
82
78
 
83
- self.baud_rate: int = (
84
- baud_rate if baud_rate is not None else self.detect_baud_rate()
85
- )
79
+ self.baud_rate: int = baud_rate if baud_rate is not None else self.detect_baud_rate()
86
80
  self.dur_tick: float = 1.0 / self.baud_rate
87
81
 
88
82
  self._add_duration()
89
83
 
90
- self.inversion: bool = (
91
- inversion if inversion is not None else self.detect_inversion()
92
- )
84
+ self.inversion: bool = inversion if inversion is not None else self.detect_inversion()
93
85
  self.half_stop: bool = self.detect_half_stop() # not needed ATM
94
86
 
95
87
  # TODO: add detectors
96
88
  self.parity: Parity = parity if parity is not None else Parity.no
97
- self.bit_order: BitOrder = (
98
- bit_order if bit_order is not None else BitOrder.lsb_first
99
- )
89
+ self.bit_order: BitOrder = bit_order if bit_order is not None else BitOrder.lsb_first
100
90
  self.frame_length: int = frame_length if frame_length is not None else 8
101
91
 
102
92
  if not (0 < self.frame_length <= 64):
@@ -146,14 +136,10 @@ class Uart:
146
136
  logger.warning("Tried to add state-duration, but it seems already present")
147
137
  return
148
138
  if not hasattr(self, "dur_tick"):
149
- raise ValueError(
150
- "Make sure that baud-rate was calculated before running add_dur()"
151
- )
139
+ raise ValueError("Make sure that baud-rate was calculated before running add_dur()")
152
140
  dur_steps = self.events_sig[1:, 0] - self.events_sig[:-1, 0]
153
141
  dur_steps = np.reshape(dur_steps, (dur_steps.size, 1))
154
- self.events_sig = np.append(
155
- self.events_sig[:-1, :], dur_steps / self.dur_tick, axis=1
156
- )
142
+ self.events_sig = np.append(self.events_sig[:-1, :], dur_steps / self.dur_tick, axis=1)
157
143
 
158
144
  def detect_inversion(self) -> bool:
159
145
  """Analyze bit-state during long pauses (unchanged states)
@@ -182,16 +168,12 @@ class Uart:
182
168
  def detect_half_stop(self) -> bool:
183
169
  """Looks into the spacing between time-steps"""
184
170
  events = self.events_sig[:1000, :] # speedup for large datasets
185
- return (
186
- np.sum((events > 1.333 * self.dur_tick) & (events < 1.667 * self.dur_tick))
187
- > 0
188
- )
171
+ return np.sum((events > 1.333 * self.dur_tick) & (events < 1.667 * self.dur_tick)) > 0
189
172
 
190
173
  def detect_dataframe_length(self) -> int:
191
174
  """Look after longest pauses
192
175
  - accumulate steps until a state with uneven step-size is found
193
176
  """
194
- pass
195
177
 
196
178
  def get_symbols(self, *, force_redo: bool = False) -> np.ndarray:
197
179
  """Ways to detect EOF:
@@ -25,9 +25,7 @@ except ImportError:
25
25
  "cffi",
26
26
  ]
27
27
  # only update when module is not avail
28
- MOCK_MODULES = [
29
- mod_name for mod_name in MOCK_MODULES if find_spec(mod_name) is None
30
- ]
28
+ MOCK_MODULES = [mod_name for mod_name in MOCK_MODULES if find_spec(mod_name) is None]
31
29
  sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
32
30
 
33
31
  from .converter import base64_to_file
@@ -22,9 +22,7 @@ def firmware_to_hex(file_path: Path) -> Path:
22
22
  return elf_to_hex(file_path)
23
23
  if is_hex(file_path):
24
24
  return file_path
25
- raise ValueError(
26
- "FW2Hex: unknown file '%s', it should be ELF or HEX", file_path.name
27
- )
25
+ raise ValueError("FW2Hex: unknown file '%s', it should be ELF or HEX", file_path.name)
28
26
 
29
27
 
30
28
  @validate_call
@@ -72,9 +70,7 @@ def base64_to_hash(content: str) -> str:
72
70
 
73
71
 
74
72
  @validate_call
75
- def extract_firmware(
76
- data: Union[str, Path], data_type: FirmwareDType, file_path: Path
77
- ) -> Path:
73
+ def extract_firmware(data: Union[str, Path], data_type: FirmwareDType, file_path: Path) -> Path:
78
74
  """- base64-string will be transformed into file
79
75
  - if data is a path the file will be copied to the destination
80
76
  """
@@ -90,14 +86,10 @@ def extract_firmware(
90
86
  elif data_type == FirmwareDType.path_hex:
91
87
  file = file_path.with_suffix(".hex")
92
88
  else:
93
- raise ValueError(
94
- "FW-Extraction failed due to unknown datatype '%s'", data_type
95
- )
89
+ raise ValueError("FW-Extraction failed due to unknown datatype '%s'", data_type)
96
90
  if not file.parent.exists():
97
91
  file.parent.mkdir(parents=True)
98
92
  shutil.copy(data, file)
99
93
  else:
100
- raise ValueError(
101
- "FW-Extraction failed due to unknown data-type '%s'", type(data)
102
- )
94
+ raise ValueError("FW-Extraction failed due to unknown data-type '%s'", type(data))
103
95
  return file
@@ -46,9 +46,7 @@ def find_symbol(file_elf: Path, symbol: str) -> bool:
46
46
 
47
47
 
48
48
  @validate_call
49
- def read_symbol(
50
- file_elf: Path, symbol: str, length: int = uid_len_default
51
- ) -> Optional[int]:
49
+ def read_symbol(file_elf: Path, symbol: str, length: int = uid_len_default) -> Optional[int]:
52
50
  """Interpreted as int"""
53
51
  if not find_symbol(file_elf, symbol):
54
52
  return None
@@ -99,9 +97,7 @@ def modify_symbol_value(
99
97
  value_raw = elf.read(address=addr, count=uid_len_default)[-uid_len_default:]
100
98
  # ⤷ cutting needed -> msp produces 4b instead of 2
101
99
  value_old = int.from_bytes(bytes=value_raw, byteorder=elf.endian, signed=False)
102
- value_raw = value.to_bytes(
103
- length=uid_len_default, byteorder=elf.endian, signed=False
104
- )
100
+ value_raw = value.to_bytes(length=uid_len_default, byteorder=elf.endian, signed=False)
105
101
  try:
106
102
  elf.write(address=addr, data=value_raw)
107
103
  except AttributeError:
@@ -110,9 +106,7 @@ def modify_symbol_value(
110
106
  if overwrite:
111
107
  file_new = file_elf
112
108
  else:
113
- file_new = file_elf.with_name(
114
- file_elf.stem + "_" + str(value) + file_elf.suffix
115
- )
109
+ file_new = file_elf.with_name(file_elf.stem + "_" + str(value) + file_elf.suffix)
116
110
  # could be simplified, but py3.8-- doesn't know .with_stem()
117
111
  elf.save(path=file_new)
118
112
  elf.close()
@@ -127,6 +121,4 @@ def modify_symbol_value(
127
121
 
128
122
 
129
123
  def modify_uid(file_elf: Path, value: int) -> Optional[Path]:
130
- return modify_symbol_value(
131
- file_elf, symbol=uid_str_default, value=value, overwrite=True
132
- )
124
+ return modify_symbol_value(file_elf, symbol=uid_str_default, value=value, overwrite=True)
@@ -30,15 +30,9 @@ class Inventory(PythonInventory, SystemInventory, TargetInventory):
30
30
  @classmethod
31
31
  def collect(cls) -> Self:
32
32
  # one by one for more precise error messages
33
- pid = PythonInventory.collect().model_dump(
34
- exclude_unset=True, exclude_defaults=True
35
- )
36
- sid = SystemInventory.collect().model_dump(
37
- exclude_unset=True, exclude_defaults=True
38
- )
39
- tid = TargetInventory.collect().model_dump(
40
- exclude_unset=True, exclude_defaults=True
41
- )
33
+ pid = PythonInventory.collect().model_dump(exclude_unset=True, exclude_defaults=True)
34
+ sid = SystemInventory.collect().model_dump(exclude_unset=True, exclude_defaults=True)
35
+ tid = TargetInventory.collect().model_dump(exclude_unset=True, exclude_defaults=True)
42
36
  model = {**pid, **sid, **tid}
43
37
  return cls(**model)
44
38