shepherd-core 2025.5.2__py3-none-any.whl → 2025.6.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 (44) hide show
  1. shepherd_core/commons.py +3 -5
  2. shepherd_core/config.py +34 -0
  3. shepherd_core/data_models/__init__.py +2 -2
  4. shepherd_core/data_models/base/calibration.py +13 -8
  5. shepherd_core/data_models/base/content.py +4 -13
  6. shepherd_core/data_models/base/wrapper.py +4 -4
  7. shepherd_core/data_models/content/_external_fixtures.yaml +11 -11
  8. shepherd_core/data_models/content/energy_environment.py +1 -1
  9. shepherd_core/data_models/content/firmware.py +10 -5
  10. shepherd_core/data_models/content/virtual_harvester.py +256 -27
  11. shepherd_core/data_models/content/virtual_source.py +37 -28
  12. shepherd_core/data_models/content/virtual_source_fixture.yaml +1 -1
  13. shepherd_core/data_models/experiment/experiment.py +29 -19
  14. shepherd_core/data_models/experiment/observer_features.py +64 -28
  15. shepherd_core/data_models/experiment/target_config.py +19 -9
  16. shepherd_core/data_models/task/emulation.py +45 -32
  17. shepherd_core/data_models/task/firmware_mod.py +1 -1
  18. shepherd_core/data_models/task/harvest.py +16 -14
  19. shepherd_core/data_models/task/observer_tasks.py +8 -6
  20. shepherd_core/data_models/task/programming.py +3 -2
  21. shepherd_core/data_models/task/testbed_tasks.py +7 -9
  22. shepherd_core/data_models/testbed/cape_fixture.yaml +9 -1
  23. shepherd_core/data_models/testbed/gpio.py +7 -0
  24. shepherd_core/data_models/testbed/observer.py +1 -1
  25. shepherd_core/data_models/testbed/observer_fixture.yaml +19 -2
  26. shepherd_core/data_models/testbed/target.py +1 -1
  27. shepherd_core/data_models/testbed/target_fixture.old1 +1 -1
  28. shepherd_core/data_models/testbed/target_fixture.yaml +14 -1
  29. shepherd_core/data_models/testbed/testbed.py +8 -9
  30. shepherd_core/data_models/testbed/testbed_fixture.yaml +11 -0
  31. shepherd_core/fw_tools/patcher.py +7 -8
  32. shepherd_core/inventory/system.py +1 -3
  33. shepherd_core/reader.py +15 -7
  34. shepherd_core/testbed_client/cache_path.py +1 -1
  35. shepherd_core/testbed_client/client_web.py +2 -2
  36. shepherd_core/testbed_client/fixtures.py +13 -11
  37. shepherd_core/testbed_client/user_model.py +3 -6
  38. shepherd_core/version.py +1 -1
  39. shepherd_core/writer.py +2 -2
  40. {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/METADATA +12 -12
  41. {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/RECORD +44 -43
  42. {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/WHEEL +1 -1
  43. {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/top_level.txt +0 -0
  44. {shepherd_core-2025.5.2.dist-info → shepherd_core-2025.6.1.dist-info}/zip-safe +0 -0
shepherd_core/commons.py CHANGED
@@ -1,8 +1,6 @@
1
1
  """Container for commonly shared constants."""
2
2
 
3
- SAMPLERATE_SPS_DEFAULT: int = 100_000
3
+ from .config import config
4
4
 
5
- UID_NAME: str = "SHEPHERD_NODE_ID"
6
- UID_SIZE: int = 2
7
-
8
- TESTBED_SERVER_URI: str = "http://127.0.0.1:8000/shepherd"
5
+ # TODO: deprecated - replace with config in subprojects and then remove here
6
+ SAMPLERATE_SPS_DEFAULT: int = config.SAMPLERATE_SPS
@@ -0,0 +1,34 @@
1
+ """Container for a common configuration.
2
+
3
+ This can be adapted by the user by importing 'config' and changing its variables.
4
+ """
5
+
6
+ from pydantic import BaseModel
7
+ from pydantic import HttpUrl
8
+
9
+
10
+ class ConfigDefault(BaseModel):
11
+ """Container for a common configuration."""
12
+
13
+ __slots__ = ()
14
+
15
+ TESTBED: str = "shepherd_tud_nes"
16
+ """name of the testbed to validate against - if enabled - see switch below"""
17
+ VALIDATE_INFRA: bool = True
18
+ """switch to turn on / off deep validation of data models also considering the current
19
+ layout & infrastructure of the testbed.
20
+ """
21
+
22
+ SAMPLERATE_SPS: int = 100_000
23
+ """Rate of IV-Recording of the testbed."""
24
+
25
+ UID_NAME: str = "SHEPHERD_NODE_ID"
26
+ """Variable name to patch in ELF-file"""
27
+ UID_SIZE: int = 2
28
+ """Variable size in Byte"""
29
+
30
+ TESTBED_SERVER: HttpUrl = "https://shepherd.cfaed.tu-dresden.de:8000/"
31
+ """Server that holds up to date testbed fixtures"""
32
+
33
+
34
+ config = ConfigDefault()
@@ -31,7 +31,7 @@ from .experiment.observer_features import GpioLevel
31
31
  from .experiment.observer_features import GpioTracing
32
32
  from .experiment.observer_features import PowerTracing
33
33
  from .experiment.observer_features import SystemLogging
34
- from .experiment.observer_features import UartTracing
34
+ from .experiment.observer_features import UartLogging
35
35
  from .experiment.target_config import TargetConfig
36
36
 
37
37
  __all__ = [
@@ -55,7 +55,7 @@ __all__ = [
55
55
  "ShpModel",
56
56
  "SystemLogging",
57
57
  "TargetConfig",
58
- "UartTracing",
58
+ "UartLogging",
59
59
  "VirtualHarvesterConfig",
60
60
  "VirtualSourceConfig",
61
61
  "Wrapper",
@@ -95,14 +95,19 @@ cal_hrv_legacy = { # legacy translator
95
95
  "adc_voltage": "adc_V_Sense", # datalog voltage
96
96
  }
97
97
 
98
+ # defaults (pre-init complex types)
99
+ cal_pair_dac_V = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
100
+ cal_pair_adc_V = CalibrationPair.from_fn(adc_voltage_to_raw, unit="V")
101
+ cal_pair_adc_C = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
102
+
98
103
 
99
104
  class CalibrationHarvester(ShpModel):
100
105
  """Container for all calibration-pairs for that device."""
101
106
 
102
- dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
103
- dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
104
- adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw, unit="V")
105
- adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
107
+ dac_V_Hrv: CalibrationPair = cal_pair_dac_V
108
+ dac_V_Sim: CalibrationPair = cal_pair_dac_V
109
+ adc_V_Sense: CalibrationPair = cal_pair_adc_V
110
+ adc_C_Hrv: CalibrationPair = cal_pair_adc_C
106
111
 
107
112
  def export_for_sysfs(self) -> dict:
108
113
  """Convert and write the essential data.
@@ -143,10 +148,10 @@ class CalibrationEmulator(ShpModel):
143
148
  Differentiates between both target-ports A/B.
144
149
  """
145
150
 
146
- dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
147
- dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
148
- adc_C_A: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
149
- adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
151
+ dac_V_A: CalibrationPair = cal_pair_dac_V
152
+ dac_V_B: CalibrationPair = cal_pair_dac_V
153
+ adc_C_A: CalibrationPair = cal_pair_adc_C
154
+ adc_C_B: CalibrationPair = cal_pair_adc_C
150
155
 
151
156
  def export_for_sysfs(self) -> dict:
152
157
  """Convert and write the essential data.
@@ -1,21 +1,16 @@
1
1
  """Base-Model for all content."""
2
2
 
3
- import hashlib
4
3
  from datetime import datetime
5
4
  from typing import Annotated
6
5
  from typing import Optional
7
- from typing import Union
8
6
  from uuid import uuid4
9
7
 
10
- from pydantic import UUID4
11
8
  from pydantic import Field
12
9
  from pydantic import StringConstraints
13
10
  from pydantic import model_validator
14
11
  from typing_extensions import Self
15
- from typing_extensions import deprecated
16
12
 
17
13
  from .shepherd import ShpModel
18
- from .timezone import local_now
19
14
 
20
15
  # constr -> to_lower=True, max_length=16, regex=r"^[\w]+$"
21
16
  # ⤷ Regex = AlphaNum
@@ -26,24 +21,20 @@ SafeStr = Annotated[str, StringConstraints(pattern=r"^[ -~]+$")]
26
21
  # ⤷ Regex = All Printable ASCII-Characters with Space
27
22
 
28
23
 
29
- @deprecated("use UUID4 instead")
30
24
  def id_default() -> int:
31
25
  """Generate a unique ID - usable as default value.
32
26
 
33
- Note: IdInt has space for 128 bit, so 128/4 = 32 hex-chars
27
+ Note: IdInt in mongoDB can currently handle 8 bytes (16 hex-values)
34
28
  """
35
- time_stamp = str(local_now()).encode("utf-8")
36
- time_hash = hashlib.sha3_224(time_stamp).hexdigest()[-32:]
37
- return int(time_hash, 16)
29
+ return int(uuid4().hex[-15:], 16)
38
30
 
39
31
 
40
32
  class ContentModel(ShpModel):
41
33
  """Base-Model for content with generalized properties."""
42
34
 
43
- # id: UUID4 = Field( # TODO: db-migration - temp fix for documentation
44
- id: Union[UUID4, int] = Field(
35
+ id: int = Field(
45
36
  description="Unique ID",
46
- default_factory=uuid4,
37
+ default_factory=id_default,
47
38
  )
48
39
  name: NameStr
49
40
  description: Annotated[Optional[SafeStr], Field(description="Required when public")] = None
@@ -17,11 +17,11 @@ class Wrapper(BaseModel):
17
17
  """Generalized web- & file-interface for all models with dynamic typecasting."""
18
18
 
19
19
  datatype: str
20
- # ⤷ model-name
20
+ """ ⤷ model-name"""
21
21
  comment: Optional[SafeStrClone] = None
22
22
  created: Optional[datetime] = None
23
- # ⤷ Optional metadata
23
+ """ ⤷ Optional metadata"""
24
24
  lib_ver: Optional[str] = version
25
- # ⤷ for debug-purposes and later comp-checks
25
+ """ ⤷ for debug-purposes and later compatibility-checks"""
26
26
  parameters: dict
27
- # ⤷ ShpModel
27
+ """ ⤷ ShpModel"""
@@ -1,7 +1,7 @@
1
1
  - datatype: EnergyEnvironment
2
2
  created: 2025-05-12 17:28:39.756999+02:00
3
3
  parameters:
4
- id: 2639560972524229652
4
+ id: 263956097252422
5
5
  name: eenv_static_2000mV_10mA_3600s
6
6
  description: Artificial static Energy Environment, 2000mV, 10mA, 3600s
7
7
  comment: null
@@ -24,7 +24,7 @@
24
24
  - datatype: EnergyEnvironment
25
25
  created: 2025-05-12 17:28:39.764595+02:00
26
26
  parameters:
27
- id: 9823394105967169626
27
+ id: 98233941059
28
28
  name: eenv_static_2000mV_1mA_3600s
29
29
  description: Artificial static Energy Environment, 2000mV, 1mA, 3600s
30
30
  comment: null
@@ -47,7 +47,7 @@
47
47
  - datatype: EnergyEnvironment
48
48
  created: 2025-05-12 17:28:39.772144+02:00
49
49
  parameters:
50
- id: 3900615675169501222
50
+ id: 39006156751
51
51
  name: eenv_static_2000mV_50mA_3600s
52
52
  description: Artificial static Energy Environment, 2000mV, 50mA, 3600s
53
53
  comment: null
@@ -70,7 +70,7 @@
70
70
  - datatype: EnergyEnvironment
71
71
  created: 2025-05-12 17:28:39.779737+02:00
72
72
  parameters:
73
- id: 14796673729431137386
73
+ id: 1479667372943113
74
74
  name: eenv_static_2000mV_5mA_3600s
75
75
  description: Artificial static Energy Environment, 2000mV, 5mA, 3600s
76
76
  comment: null
@@ -93,7 +93,7 @@
93
93
  - datatype: EnergyEnvironment
94
94
  created: 2025-05-12 17:28:39.787329+02:00
95
95
  parameters:
96
- id: 6648482606607441403
96
+ id: 664848260660744
97
97
  name: eenv_static_3000mV_10mA_3600s
98
98
  description: Artificial static Energy Environment, 3000mV, 10mA, 3600s
99
99
  comment: null
@@ -116,7 +116,7 @@
116
116
  - datatype: EnergyEnvironment
117
117
  created: 2025-05-12 17:28:39.794922+02:00
118
118
  parameters:
119
- id: 13566000951043177991
119
+ id: 1356600095104317
120
120
  name: eenv_static_3000mV_1mA_3600s
121
121
  description: Artificial static Energy Environment, 3000mV, 1mA, 3600s
122
122
  comment: null
@@ -139,7 +139,7 @@
139
139
  - datatype: EnergyEnvironment
140
140
  created: 2025-05-12 17:28:39.802410+02:00
141
141
  parameters:
142
- id: 7977778327156610158
142
+ id: 797777832715661
143
143
  name: eenv_static_3000mV_50mA_3600s
144
144
  description: Artificial static Energy Environment, 3000mV, 50mA, 3600s
145
145
  comment: null
@@ -162,7 +162,7 @@
162
162
  - datatype: EnergyEnvironment
163
163
  created: 2025-05-12 17:28:39.810147+02:00
164
164
  parameters:
165
- id: 4900162978999238419
165
+ id: 490016297899923
166
166
  name: eenv_static_3000mV_5mA_3600s
167
167
  description: Artificial static Energy Environment, 3000mV, 5mA, 3600s
168
168
  comment: null
@@ -269,7 +269,7 @@
269
269
  - datatype: Firmware
270
270
  created: 2025-05-12 17:28:39.851412+02:00
271
271
  parameters:
272
- id: 7163917825449888392
272
+ id: 716391782544988
273
273
  name: nrf52_deep_sleep
274
274
  description: practically turned off MCU with the lowest possible consumption
275
275
  comment: null
@@ -325,7 +325,7 @@
325
325
  - datatype: Firmware
326
326
  created: 2025-05-12 17:28:39.868340+02:00
327
327
  parameters:
328
- id: 3174430733058172825
328
+ id: 317443073305817
329
329
  name: nrf52_rf_survey
330
330
  description: Link Matrix Generator - TX-Unit - sends packet with every possible
331
331
  P_TX, loops until stopped
@@ -354,7 +354,7 @@
354
354
  - datatype: Firmware
355
355
  created: 2025-05-12 17:28:39.876819+02:00
356
356
  parameters:
357
- id: 16381936580724580968
357
+ id: 1638193658072458
358
358
  name: nrf52_rf_test
359
359
  description: sends out 1 BLE-Packet per second (verify with an app like 'RaMBLE')
360
360
  comment: null
@@ -28,7 +28,7 @@ class EnergyEnvironment(ContentModel):
28
28
  data_path: Path
29
29
  data_type: EnergyDType
30
30
  data_local: bool = True
31
- # ⤷ signals that file has to be copied to testbed
31
+ """ ⤷ signals that file has to be copied to testbed"""
32
32
 
33
33
  duration: PositiveFloat
34
34
  energy_Ws: PositiveFloat
@@ -62,7 +62,7 @@ class Firmware(ContentModel, title="Firmware of Target"):
62
62
  data_type: FirmwareDType
63
63
  data_hash: Optional[str] = None
64
64
  data_local: bool = True
65
- # ⤷ signals that file has to be copied to testbed
65
+ """ ⤷ signals that file has to be copied to testbed"""
66
66
 
67
67
  @model_validator(mode="before")
68
68
  @classmethod
@@ -125,12 +125,17 @@ class Firmware(ContentModel, title="Firmware of Target"):
125
125
  if "mcu" not in kwargs:
126
126
  kwargs["mcu"] = arch_to_mcu[arch]
127
127
 
128
+ # verification of ELF - warn if something is off
129
+ # -> adds ARCH if it is able to derive
128
130
  if kwargs["data_type"] == FirmwareDType.base64_elf:
129
131
  arch = fw_tools.read_arch(file)
130
- if "msp430" in arch and not fw_tools.is_elf_msp430(file):
131
- raise ValueError("File is not a ELF for msp430")
132
- if ("nrf52" in arch or "arm" in arch) and not fw_tools.is_elf_nrf52(file):
133
- raise ValueError("File is not a ELF for nRF52")
132
+ try:
133
+ if "msp430" in arch and not fw_tools.is_elf_msp430(file):
134
+ raise ValueError("File is not a ELF for msp430")
135
+ if ("nrf52" in arch or "arm" in arch) and not fw_tools.is_elf_nrf52(file):
136
+ raise ValueError("File is not a ELF for nRF52")
137
+ except RuntimeError:
138
+ logger.warning("ObjCopy not found -> Arch of Firmware can't be verified")
134
139
  logger.debug("ELF-File '%s' has arch: %s", file.name, arch)
135
140
  if "mcu" not in kwargs:
136
141
  kwargs["mcu"] = arch_to_mcu[arch]
@@ -10,7 +10,7 @@ from pydantic import Field
10
10
  from pydantic import model_validator
11
11
  from typing_extensions import Self
12
12
 
13
- from shepherd_core.commons import SAMPLERATE_SPS_DEFAULT
13
+ from shepherd_core.config import config
14
14
  from shepherd_core.data_models.base.calibration import CalibrationHarvester
15
15
  from shepherd_core.data_models.base.content import ContentModel
16
16
  from shepherd_core.data_models.base.shepherd import ShpModel
@@ -23,54 +23,283 @@ from .energy_environment import EnergyDType
23
23
  class AlgorithmDType(str, Enum):
24
24
  """Options for choosing a harvesting algorithm."""
25
25
 
26
- direct = disable = neutral = "neutral" # for just using IVTrace / samples
27
- isc_voc = "isc_voc" # only recordable ATM
26
+ direct = disable = neutral = "neutral"
27
+ """
28
+ Reads an energy environment as is without selecting a harvesting
29
+ voltage.
30
+
31
+ Used to play "constant-power" energy environments or simple
32
+ "on-off-patterns". Generally, not useful for virtual source
33
+ emulation.
34
+
35
+ Not applicable to real harvesting, only emulation with IVTrace / samples.
36
+ """
37
+
38
+ isc_voc = "isc_voc"
39
+ """
40
+ Short Circuit Current, Open Circuit Voltage.
41
+
42
+ This is not relevant for emulation, but used to configure recording of
43
+ energy environments.
44
+
45
+ This mode samples the two extremes of an IV curve, which may be
46
+ interesting to characterize a transducer/energy environment.
47
+
48
+ Not applicable to emulation - only recordable during harvest-recording ATM.
49
+ """
50
+
28
51
  ivcurve = ivcurves = ivsurface = "ivcurve"
52
+ """
53
+ Used during harvesting to record the full IV surface.
54
+
55
+ When configuring the energy environment recording, this algorithm
56
+ records the IV surface by repeatedly recording voltage and current
57
+ while ramping the voltage.
58
+
59
+ Cannot be used as output of emulation.
60
+ """
61
+
29
62
  constant = cv = "cv"
63
+ """
64
+ Harvest energy at a fixed predefined voltage ('voltage_mV').
65
+
66
+ For harvesting, this records the IV samples at the specified voltage.
67
+ For emulation, this virtually harvests the IV surface at the specified voltage.
68
+
69
+ In addition to constant voltage harvesting, this can be used together
70
+ with the 'feedback_to_hrv' flag to implement a "Capacitor and Diode"
71
+ topology, where the harvesting voltage depends dynamically on the
72
+ capacitor voltage.
73
+ """
74
+
30
75
  # ci .. constant current -> is this desired?
76
+
31
77
  mppt_voc = "mppt_voc"
78
+ """
79
+ Emulate a harvester with maximum power point (MPP) tracking based on
80
+ open circuit voltage measurements.
81
+
82
+ This MPPT heuristic estimates the MPP as a constant ratio of the open
83
+ circuit voltage.
84
+
85
+ Used in conjunction with 'setpoint_n', 'interval_ms', and 'duration_ms'.
86
+ """
87
+
32
88
  mppt_po = perturb_observe = "mppt_po"
89
+ """
90
+ Emulate a harvester with perturb and observe maximum power point
91
+ tracking.
92
+
93
+ This MPPT heuristic adjusts the harvesting voltage by small amounts and
94
+ checks if the power increases. Eventually, the tracking changes the
95
+ direction of adjustments and oscillates around the MPP.
96
+ """
97
+
33
98
  mppt_opt = optimal = "mppt_opt"
99
+ """
100
+ A theoretical harvester that identifies the MPP by reading it from the
101
+ IV curve during emulation.
34
102
 
103
+ Note that this is not possible for real-world harvesting as the system would
104
+ not know the entire IV curve. In that case a very fast and detailed mppt_po is
105
+ used.
106
+ """
35
107
 
36
- class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
37
- """A vHrv makes a source-characterization (i.e. ivcurve) usable for the vSrc.
38
108
 
39
- Mostly used when the file-based energy environment of the virtual source
40
- is not already supplied as pre-harvested ivtrace.
109
+ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
110
+ """The virtual harvester configuration characterizes usage of an energy environment.
111
+
112
+ It is used to both harvesting during emulation and to record
113
+ energy environments (sometimes referred to as "harvesting traces").
114
+
115
+ For emulation:
116
+
117
+ The virtual harvester configuration describes how energy from a recorded
118
+ energy environment is harvested. Typically, the energy environment provides
119
+ an IV-surface, which is a continuous function in three dimensions: voltage,
120
+ current, and time. Based on this surface, the emulation can derive the
121
+ available IV-curve at each point in time. The harvester looks up the current
122
+ that is available (according to the energy environment) from a given
123
+ harvesting voltage. The harvesting voltage may be dynamically chosen by the
124
+ harvester based on the implemented harvesting algorithm, which models
125
+ different real-world harvesters. For example, a maximum power point tracking
126
+ harvester may choose a harvesting voltage as a ratio of the open circuit
127
+ voltage available from the energy environment (or transducer in practice).
128
+
129
+ The energy environments are encoded not as a three-dimensional function, but
130
+ as IV tuples over time (sampled at a constant frequency). This originates
131
+ from the technical implementation when recording the IV-surface, where the
132
+ recorder provides the IV-curve by measuring the current for a given voltage
133
+ and ramping the voltage from minimal to maximum.
134
+
135
+ For harvest-recordings:
136
+
137
+ An energy environment is fully described by the IV surface, which are IV
138
+ curves over time. Shepherd approximates this by sampling the current at
139
+ equidistant steps of a voltage ramp. The VirtualHarvesterConfig is also used
140
+ to parameterize the recording process, typically, it should be configured to
141
+ record a full IV surface, as this contains the full information of the energy
142
+ environment. The postponed harvesting is then performed during emulation.
143
+
144
+ However, it is also possible to record a "pre-harvested" energy environment
145
+ by performing the harvesting during recording. This results in a recording
146
+ containing IV samples over time that represent the harvesting voltage
147
+ (chosen by the virtual harvester during recording) and the current available
148
+ from the energy environment for that voltage. Together, these represent the
149
+ power available for harvesting at the time, and during emulation, this power
150
+ can be converted by the input stage (boost converter) to charge the energy
151
+ storage.
41
152
  """
42
153
 
43
154
  # General Metadata & Ownership -> ContentModel
44
155
 
45
156
  algorithm: AlgorithmDType
46
- # used to harvest energy
157
+ """The algorithm determines how the harvester chooses the harvesting voltage.
158
+ """
47
159
 
48
160
  samples_n: Annotated[int, Field(ge=8, le=2_000)] = 8
49
- # for & of ivcurve (and more?`)
161
+ """How many IV samples are measured for one IV curve.
162
+
163
+ The curve is recorded by measuring the el. current available from the
164
+ transducer at equidistant voltage intervals. These voltages are
165
+ probed by ramping between `voltage_min_mV` and `voltage_max_mV` at
166
+ `samples_n` points equally distributed over the voltage range. After
167
+ setting the voltage, the recorder waits for a short period - allowing
168
+ the analog frontend and transducer to settle - before recording the
169
+ harvesting current. This wait duration is influenced by
170
+ `wait_cycles`.
171
+
172
+ Selecting all these parameters is a tradeoff between accuracy of the IV
173
+ curve (density of IV samples) and measurement duration, hence the time
174
+ accuracy (density of points) of the IV-surface.
175
+
176
+ Only applicable to recording, not used in emulation.
177
+
178
+ Used together with `voltage_min_mV`, `voltage_max_mV`, `rising`, and
179
+ `wait_cycles`.
180
+ """
50
181
 
51
182
  voltage_mV: Annotated[float, Field(ge=0, le=5_000)] = 2_500
52
- # starting-point for some algorithms (mppt_po)
183
+ """The harvesting voltage for constant voltage harvesting.
184
+
185
+ Additionally, for Perturb-and-Observe MPPT, this defines the voltage at
186
+ startup.
187
+ """
188
+
53
189
  voltage_min_mV: Annotated[float, Field(ge=0, le=5_000)] = 0
190
+ """Minimum voltage recorded for the IV curve.
191
+
192
+ See `samples_n` for further details.
193
+
194
+ In emulation, this can be used to "block" parts of the recorded IV curve
195
+ and not utilize them in the virtual source. However, this is generally
196
+ discouraged as it can result in discontinuities in the curve and is not
197
+ well tested.
198
+ For emulation, this value ideally corresponds to the value of the
199
+ recorded energy environment.
200
+ """
201
+
54
202
  voltage_max_mV: Annotated[float, Field(ge=0, le=5_000)] = 5_000
203
+ """Maximum voltage sampled for the curve.
204
+
205
+ See `voltage_min_mV` and `samples_n`.
206
+ """
207
+
55
208
  current_limit_uA: Annotated[float, Field(ge=1, le=50_000)] = 50_000
56
- # ⤷ allows to keep trajectory in special region (or constant current tracking)
57
- # boundary for detecting open circuit in emulated version (working on IV-Curves)
209
+ """
210
+ For MPPT VOC, the open circuit voltage is identified as the el. current
211
+ crosses below this threshold.
212
+
213
+ During recording it allows to keep trajectory in special region
214
+ (constant current tracking).
215
+ """
216
+
58
217
  voltage_step_mV: Optional[Annotated[float, Field(ge=1, le=1_000_000)]] = None
218
+ """The difference between two adjacent voltage samples.
219
+
220
+ This value is implicitly derived from the other ramp parameters:
221
+ (voltage_max_mV - voltage_min_mV) / (samples_n - 1)
222
+ """
59
223
 
60
224
  setpoint_n: Annotated[float, Field(ge=0, le=1.0)] = 0.70
61
- # ⤷ ie. for mppt_voc
225
+ """
226
+ The "Open Circuit Voltage Maximum Power Point Tracker" estimates the MPP
227
+ by taking a constant fraction defined by this parameter of the open
228
+ circuit voltage. For example, if the IV curve shows an open circuit
229
+ voltage of 2V and the setpoint is 0.75, then the harvester selects
230
+ 1.5 volts as the harvesting voltage.
231
+
232
+ This value is only relevant when 'algorithm == mppt_voc'.
233
+ """
234
+
62
235
  interval_ms: Annotated[float, Field(ge=0.01, le=1_000_000)] = 100
63
- # between start of measurements (ie. V_OC)
236
+ """The MPP is repeatedly estimated at fixed intervals defined by this duration.
237
+
238
+ Note that the energy environment can still change in between MPP
239
+ estimations, but the harvesting voltage is not updated in between.
240
+
241
+ This value is relevant for all MPP algorithms.
242
+ For Perturb and Observe, this value is the wait interval between steps.
243
+
244
+ When an energy environment is recorded with `mppt_opt`, the optimal
245
+ harvester is approximated with a very fast Perturb-Observe algorithm,
246
+ where this interval should be set to a very small value.
247
+ When emulating with `mppt_opt`, this value is not relevant as the
248
+ emulation simply picks the maximum power point from the IV-curve.
249
+ """
250
+
64
251
  duration_ms: Annotated[float, Field(ge=0.01, le=1_000_000)] = 0.1
65
- # of (open voltage) measurement
252
+ """The duration of MPP sampling.
253
+
254
+ While performing an MPP sampling every 'interval_ms', the input is
255
+ disconnected to accurately measure the open circuit voltage.
256
+
257
+ This value is only relevant for `mppt_voc`.
258
+ """
259
+
66
260
  rising: bool = True
67
- # direction of sawtooth
261
+ """Ramp direction for sampling the IV curve.
262
+
263
+ When set to true, sampling starts at the minimum voltage and ramps up to
264
+ the maximum.
265
+
266
+ See `samples_n` for further details.
267
+ Not relevant for emulation.
268
+ """
269
+
68
270
  enable_linear_extrapolation: bool = True
69
- # ⤷ improves slow cv-algo that is base of most ivcurve-harvesters
70
- # (update-freq dependent on window-size)
271
+ """
272
+ Because the IV curve is not stored fully in PRU memory but streamed
273
+ sequentially to the PRU, looking up any IV value at any time is not
274
+ possible. However, because the precision of the emulation degrades
275
+ until the relevant IV sample passes by, it can extrapolate the available data
276
+ to estimate the required IV sample.
277
+
278
+ Enabling extrapolation can yield a better numeric simulation, especially
279
+ if the harvesting voltage changes rapidly or the IV surface is steep in
280
+ relevant regions. For example, when emulating a capacitor diode
281
+ setup and the current falls at high voltages.
282
+
283
+ This value is only relevant for emulation.
284
+ """
71
285
 
72
286
  # Underlying recorder
73
287
  wait_cycles: Annotated[int, Field(ge=0, le=100)] = 1
288
+ """
289
+ The wait duration to let the analog frontend settle before taking a
290
+ measurement.
291
+
292
+ When recording the energy environment, the voltage is set by the
293
+ digital-to-analog-converter. This parameter delays the current
294
+ measurement performed by the analog-to-digital converter to allow the
295
+ harvesting transducer to settle at the defined voltage.
296
+
297
+ When recording with `IscVoc`, wait cycles should be added as the analog
298
+ changes are more significant.
299
+
300
+ Not relevant for emulation.
301
+ """
302
+
74
303
  # ⤷ first cycle: ADC-Sampling & DAC-Writing, further steps: waiting
75
304
 
76
305
  @model_validator(mode="before")
@@ -134,9 +363,9 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
134
363
  def calc_timings_ms(self, *, for_emu: bool) -> tuple[float, float]:
135
364
  """factor-in model-internal timing-constraints."""
136
365
  window_length = self.samples_n * (1 + self.wait_cycles)
137
- time_min_ms = (1 + self.wait_cycles) * 1_000 / SAMPLERATE_SPS_DEFAULT
366
+ time_min_ms = (1 + self.wait_cycles) * 1_000 / config.SAMPLERATE_SPS
138
367
  if for_emu:
139
- window_ms = window_length * 1_000 / SAMPLERATE_SPS_DEFAULT
368
+ window_ms = window_length * 1_000 / config.SAMPLERATE_SPS
140
369
  time_min_ms = max(time_min_ms, window_ms)
141
370
 
142
371
  interval_ms = min(max(self.interval_ms, time_min_ms), 1_000_000)
@@ -224,16 +453,16 @@ class HarvesterPRUConfig(ShpModel):
224
453
  voltage_min_uV: u32
225
454
  voltage_max_uV: u32
226
455
  voltage_step_uV: u32
227
- # ⤷ for window-based algo like ivcurve
456
+ """ ⤷ for window-based algo like ivcurve"""
228
457
  current_limit_nA: u32
229
- # ⤷ lower bound to detect zero current
458
+ """ ⤷ lower bound to detect zero current"""
230
459
  setpoint_n8: u32
231
460
  interval_n: u32
232
- # ⤷ between measurements
461
+ """ ⤷ between measurements"""
233
462
  duration_n: u32
234
- # ⤷ of measurement
463
+ """ ⤷ of measurement"""
235
464
  wait_cycles_n: u32
236
- # ⤷ for DAC to settle
465
+ """ ⤷ for DAC to settle"""
237
466
 
238
467
  @classmethod
239
468
  def from_vhrv(
@@ -288,7 +517,7 @@ class HarvesterPRUConfig(ShpModel):
288
517
  voltage_step_uV=round(voltage_step_mV * 10**3),
289
518
  current_limit_nA=round(data.current_limit_uA * 10**3),
290
519
  setpoint_n8=round(min(255, data.setpoint_n * 2**8)),
291
- interval_n=round(interval_ms * SAMPLERATE_SPS_DEFAULT * 1e-3),
292
- duration_n=round(duration_ms * SAMPLERATE_SPS_DEFAULT * 1e-3),
520
+ interval_n=round(interval_ms * config.SAMPLERATE_SPS * 1e-3),
521
+ duration_n=round(duration_ms * config.SAMPLERATE_SPS * 1e-3),
293
522
  wait_cycles_n=data.wait_cycles,
294
523
  )