shepherd-core 2024.8.2__py3-none-any.whl → 2024.11.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.
- shepherd_core/data_models/content/energy_environment.py +3 -0
- shepherd_core/data_models/content/virtual_harvester.py +8 -3
- shepherd_core/data_models/content/virtual_harvester_fixture.yaml +27 -16
- shepherd_core/data_models/content/virtual_source.py +22 -2
- shepherd_core/data_models/content/virtual_source_fixture.yaml +16 -2
- shepherd_core/data_models/task/emulation.py +2 -1
- shepherd_core/reader.py +3 -3
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/target_model.py +96 -17
- shepherd_core/vsource/virtual_converter_model.py +43 -35
- shepherd_core/vsource/virtual_harvester_model.py +36 -4
- shepherd_core/vsource/virtual_source_model.py +7 -1
- shepherd_core/vsource/virtual_source_simulation.py +68 -2
- shepherd_core/writer.py +47 -54
- {shepherd_core-2024.8.2.dist-info → shepherd_core-2024.11.1.dist-info}/METADATA +8 -7
- {shepherd_core-2024.8.2.dist-info → shepherd_core-2024.11.1.dist-info}/RECORD +19 -19
- {shepherd_core-2024.8.2.dist-info → shepherd_core-2024.11.1.dist-info}/WHEEL +1 -1
- {shepherd_core-2024.8.2.dist-info → shepherd_core-2024.11.1.dist-info}/top_level.txt +0 -0
- {shepherd_core-2024.8.2.dist-info → shepherd_core-2024.11.1.dist-info}/zip-safe +0 -0
|
@@ -34,6 +34,9 @@ class EnergyEnvironment(ContentModel):
|
|
|
34
34
|
valid: bool = False
|
|
35
35
|
|
|
36
36
|
# TODO: scale up/down voltage/current
|
|
37
|
+
# TODO: multiple files for one env
|
|
38
|
+
# TODO: mean power as energy/duration
|
|
39
|
+
# TODO: harvester, transducer
|
|
37
40
|
|
|
38
41
|
# additional descriptive metadata, TODO: these are very solar-centered -> generalize
|
|
39
42
|
light_source: Optional[str] = None
|
|
@@ -21,6 +21,7 @@ from .energy_environment import EnergyDType
|
|
|
21
21
|
class AlgorithmDType(str, Enum):
|
|
22
22
|
"""Options for choosing a harvesting algorithm."""
|
|
23
23
|
|
|
24
|
+
direct = disable = neutral = "neutral"
|
|
24
25
|
isc_voc = "isc_voc"
|
|
25
26
|
ivcurve = ivcurves = ("ivcurve",)
|
|
26
27
|
constant = cv = "cv"
|
|
@@ -62,6 +63,9 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
62
63
|
# ⤷ of (open voltage) measurement
|
|
63
64
|
rising: bool = True
|
|
64
65
|
# ⤷ direction of sawtooth
|
|
66
|
+
enable_linear_extrapolation: bool = True
|
|
67
|
+
# ⤷ improves slow cv-algo that is base of most ivcurve-harvesters
|
|
68
|
+
# (update-freq dependent on window-size)
|
|
65
69
|
|
|
66
70
|
# Underlying recorder
|
|
67
71
|
wait_cycles: Annotated[int, Field(ge=0, le=100)] = 1
|
|
@@ -107,7 +111,7 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
107
111
|
return self
|
|
108
112
|
|
|
109
113
|
def calc_hrv_mode(self, *, for_emu: bool) -> int:
|
|
110
|
-
return 1 * int(for_emu) + 2 * self.rising
|
|
114
|
+
return 1 * int(for_emu) + 2 * self.rising + 4 * self.enable_linear_extrapolation
|
|
111
115
|
|
|
112
116
|
def calc_algorithm_num(self, *, for_emu: bool) -> int:
|
|
113
117
|
num = algo_to_num.get(self.algorithm)
|
|
@@ -117,7 +121,7 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
117
121
|
f"current usage = {self.algorithm}"
|
|
118
122
|
)
|
|
119
123
|
raise ValueError(msg)
|
|
120
|
-
if num < algo_to_num["isc_voc"]:
|
|
124
|
+
if not for_emu and num < algo_to_num["isc_voc"]:
|
|
121
125
|
msg = (
|
|
122
126
|
f"[{self.name}] Select valid harvest-algorithm for harvester, "
|
|
123
127
|
f"current usage = {self.algorithm}"
|
|
@@ -177,7 +181,7 @@ u32 = Annotated[int, Field(ge=0, lt=2**32)]
|
|
|
177
181
|
|
|
178
182
|
# Currently implemented harvesters
|
|
179
183
|
# NOTE: numbers have meaning and will be tested ->
|
|
180
|
-
# - harvesting on "neutral" is not possible
|
|
184
|
+
# - harvesting on "neutral" is not possible - direct pass-through
|
|
181
185
|
# - emulation with "ivcurve" or lower is also resulting in Error
|
|
182
186
|
# - "_opt" has its own algo for emulation, but is only a fast mppt_po for harvesting
|
|
183
187
|
algo_to_num = {
|
|
@@ -192,6 +196,7 @@ algo_to_num = {
|
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
algo_to_dtype = {
|
|
199
|
+
"neutral": EnergyDType.ivsample,
|
|
195
200
|
"isc_voc": EnergyDType.isc_voc,
|
|
196
201
|
"ivcurve": EnergyDType.ivcurve,
|
|
197
202
|
"cv": EnergyDType.ivsample,
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
# - look into the implementation to see which parameters are used
|
|
4
4
|
# - base for neutral fallback values if provided yml is sparse
|
|
5
5
|
# - -> it is encouraged to omit redundant parameters in your own implementation
|
|
6
|
+
# - general rule: 0<id<2000 harvest from transducers, 2000<=id harvest from ivcurve
|
|
7
|
+
|
|
6
8
|
- datatype: VirtualHarvesterConfig
|
|
7
9
|
parameters:
|
|
8
10
|
id: 1000
|
|
9
11
|
name: neutral
|
|
12
|
+
enable_linear_extrapolation: true
|
|
10
13
|
owner: Ingmar
|
|
11
14
|
group: NES Lab
|
|
12
15
|
visible2group: true
|
|
@@ -16,7 +19,7 @@
|
|
|
16
19
|
|
|
17
20
|
- datatype: VirtualHarvesterConfig
|
|
18
21
|
parameters:
|
|
19
|
-
id:
|
|
22
|
+
id: 1100
|
|
20
23
|
name: ivcurve
|
|
21
24
|
description: Postpone harvesting by sampling ivcurves (voltage stepped as sawtooth-wave)
|
|
22
25
|
comment: ~110 Hz, Between 50 & 60 Hz line-frequency to avoid standing waves
|
|
@@ -32,19 +35,19 @@
|
|
|
32
35
|
|
|
33
36
|
- datatype: VirtualHarvesterConfig
|
|
34
37
|
parameters:
|
|
35
|
-
id:
|
|
38
|
+
id: 1101
|
|
36
39
|
name: ivcurves # synonym
|
|
37
40
|
inherit_from: ivcurve
|
|
38
41
|
|
|
39
42
|
- datatype: VirtualHarvesterConfig
|
|
40
43
|
parameters:
|
|
41
|
-
id:
|
|
44
|
+
id: 1103
|
|
42
45
|
name: iv110 # synonym
|
|
43
46
|
inherit_from: ivcurve
|
|
44
47
|
|
|
45
48
|
- datatype: VirtualHarvesterConfig
|
|
46
49
|
parameters:
|
|
47
|
-
id:
|
|
50
|
+
id: 1102
|
|
48
51
|
name: iv1000
|
|
49
52
|
comment: Name relates to curves per second
|
|
50
53
|
inherit_from: ivcurve
|
|
@@ -53,7 +56,7 @@
|
|
|
53
56
|
|
|
54
57
|
- datatype: VirtualHarvesterConfig
|
|
55
58
|
parameters:
|
|
56
|
-
id:
|
|
59
|
+
id: 1200
|
|
57
60
|
name: isc_voc
|
|
58
61
|
description: Postpone harvesting by sampling short circuit current & open circuit voltage
|
|
59
62
|
inherit_from: neutral
|
|
@@ -62,7 +65,7 @@
|
|
|
62
65
|
|
|
63
66
|
- datatype: VirtualHarvesterConfig
|
|
64
67
|
parameters:
|
|
65
|
-
id:
|
|
68
|
+
id: 2100
|
|
66
69
|
name: cv20
|
|
67
70
|
description: Harvesting with constant Voltage
|
|
68
71
|
inherit_from: neutral
|
|
@@ -71,28 +74,28 @@
|
|
|
71
74
|
|
|
72
75
|
- datatype: VirtualHarvesterConfig
|
|
73
76
|
parameters:
|
|
74
|
-
id:
|
|
77
|
+
id: 2101
|
|
75
78
|
name: cv24
|
|
76
79
|
inherit_from: cv20
|
|
77
80
|
voltage_mV: 2400
|
|
78
81
|
|
|
79
82
|
- datatype: VirtualHarvesterConfig
|
|
80
83
|
parameters:
|
|
81
|
-
id:
|
|
84
|
+
id: 2102
|
|
82
85
|
name: cv33
|
|
83
86
|
inherit_from: cv20
|
|
84
87
|
voltage_mV: 3300
|
|
85
88
|
|
|
86
89
|
- datatype: VirtualHarvesterConfig
|
|
87
90
|
parameters:
|
|
88
|
-
id:
|
|
91
|
+
id: 2102
|
|
89
92
|
name: cv10
|
|
90
93
|
inherit_from: cv20
|
|
91
94
|
voltage_mV: 1000
|
|
92
95
|
|
|
93
96
|
- datatype: VirtualHarvesterConfig
|
|
94
97
|
parameters:
|
|
95
|
-
id:
|
|
98
|
+
id: 2200
|
|
96
99
|
name: mppt_voc
|
|
97
100
|
description: MPPT based on open circuit voltage for solar
|
|
98
101
|
inherit_from: neutral
|
|
@@ -104,7 +107,7 @@
|
|
|
104
107
|
|
|
105
108
|
- datatype: VirtualHarvesterConfig
|
|
106
109
|
parameters:
|
|
107
|
-
id:
|
|
110
|
+
id: 2201
|
|
108
111
|
name: mppt_bq
|
|
109
112
|
description: MPPT of TI BQ-Converters for solar
|
|
110
113
|
inherit_from: mppt_voc
|
|
@@ -114,7 +117,7 @@
|
|
|
114
117
|
|
|
115
118
|
- datatype: VirtualHarvesterConfig
|
|
116
119
|
parameters:
|
|
117
|
-
id:
|
|
120
|
+
id: 2202
|
|
118
121
|
name: mppt_bqt
|
|
119
122
|
description: MPPT of TI BQ-Converters for thermoelectric
|
|
120
123
|
inherit_from: mppt_voc
|
|
@@ -124,19 +127,19 @@
|
|
|
124
127
|
|
|
125
128
|
- datatype: VirtualHarvesterConfig
|
|
126
129
|
parameters:
|
|
127
|
-
id:
|
|
130
|
+
id: 2203
|
|
128
131
|
name: mppt_bq_solar # explicit naming
|
|
129
132
|
inherit_from: mppt_bq
|
|
130
133
|
|
|
131
134
|
- datatype: VirtualHarvesterConfig
|
|
132
135
|
parameters:
|
|
133
|
-
id:
|
|
136
|
+
id: 2204
|
|
134
137
|
name: mppt_bq_thermoelectric # explicit naming
|
|
135
138
|
inherit_from: mppt_bqt
|
|
136
139
|
|
|
137
140
|
- datatype: VirtualHarvesterConfig
|
|
138
141
|
parameters:
|
|
139
|
-
id:
|
|
142
|
+
id: 2205
|
|
140
143
|
name: mppt_po
|
|
141
144
|
description: MPPT based on perturb & observe algorithm
|
|
142
145
|
inherit_from: neutral
|
|
@@ -148,10 +151,18 @@
|
|
|
148
151
|
|
|
149
152
|
- datatype: VirtualHarvesterConfig
|
|
150
153
|
parameters:
|
|
151
|
-
id:
|
|
154
|
+
id: 2206
|
|
152
155
|
name: mppt_opt
|
|
153
156
|
description: Power-Optimum with very fast PO-Variant (harvesting) or special max-pwr-picker (emulator / ivcurve)
|
|
154
157
|
inherit_from: mppt_po
|
|
155
158
|
algorithm: mppt_opt
|
|
156
159
|
voltage_step_mV: 1
|
|
157
160
|
interval_ms: 0.01
|
|
161
|
+
|
|
162
|
+
- datatype: VirtualHarvesterConfig
|
|
163
|
+
parameters:
|
|
164
|
+
id: 3000
|
|
165
|
+
name: direct
|
|
166
|
+
description: Disables harvesting, even for ivcurve-input
|
|
167
|
+
inherit_from: neutral
|
|
168
|
+
algorithm: neutral
|
|
@@ -12,6 +12,7 @@ from ...logger import logger
|
|
|
12
12
|
from ...testbed_client import tb_client
|
|
13
13
|
from ..base.content import ContentModel
|
|
14
14
|
from ..base.shepherd import ShpModel
|
|
15
|
+
from .energy_environment import EnergyDType
|
|
15
16
|
from .virtual_harvester import HarvesterPRUConfig
|
|
16
17
|
from .virtual_harvester import VirtualHarvesterConfig
|
|
17
18
|
|
|
@@ -42,6 +43,8 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
42
43
|
# ⤷ if false -> v_intermediate = v_input, output-switch-hysteresis is still usable
|
|
43
44
|
enable_buck: bool = False
|
|
44
45
|
# ⤷ if false -> v_output = v_intermediate
|
|
46
|
+
enable_feedback_to_hrv: bool = False
|
|
47
|
+
# src can control a cv-harvester for ivcurve
|
|
45
48
|
|
|
46
49
|
interval_startup_delay_drain_ms: Annotated[float, Field(ge=0, le=10_000)] = 0
|
|
47
50
|
|
|
@@ -200,7 +203,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
200
203
|
values["V_disable_output_threshold_mV"] = self.V_intermediate_disable_threshold_mV
|
|
201
204
|
return values
|
|
202
205
|
|
|
203
|
-
def calc_converter_mode(self, *, log_intermediate_node: bool) -> int:
|
|
206
|
+
def calc_converter_mode(self, dtype_in: EnergyDType, *, log_intermediate_node: bool) -> int:
|
|
204
207
|
"""Assembles bitmask from discrete values.
|
|
205
208
|
|
|
206
209
|
log_intermediate_node: record / log virtual intermediate (cap-)voltage and
|
|
@@ -208,11 +211,25 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
208
211
|
"""
|
|
209
212
|
enable_storage = self.C_intermediate_uF > 0
|
|
210
213
|
enable_boost = self.enable_boost and enable_storage
|
|
214
|
+
if enable_boost != self.enable_boost:
|
|
215
|
+
logger.warning("VSrc - boost was disabled due to missing storage capacitor!")
|
|
216
|
+
enable_feedback = (
|
|
217
|
+
self.enable_feedback_to_hrv
|
|
218
|
+
and enable_storage
|
|
219
|
+
and not enable_boost
|
|
220
|
+
and dtype_in == EnergyDType.ivcurve
|
|
221
|
+
)
|
|
222
|
+
if enable_feedback != self.enable_feedback_to_hrv:
|
|
223
|
+
reason = "enabled boost, " if enable_boost else ""
|
|
224
|
+
reason += "" if dtype_in == EnergyDType.ivcurve else "input not ivcurve, "
|
|
225
|
+
reason += "" if enable_storage else "no storage capacitor"
|
|
226
|
+
logger.warning("VSRC - feedback to harvester was disabled! Reasons: %s", reason)
|
|
211
227
|
return (
|
|
212
228
|
1 * int(enable_storage)
|
|
213
229
|
+ 2 * int(enable_boost)
|
|
214
230
|
+ 4 * int(self.enable_buck)
|
|
215
231
|
+ 8 * int(log_intermediate_node)
|
|
232
|
+
+ 16 * int(enable_feedback)
|
|
216
233
|
)
|
|
217
234
|
|
|
218
235
|
def calc_cap_constant_us_per_nF_n28(self) -> int:
|
|
@@ -285,13 +302,16 @@ class ConverterPRUConfig(ShpModel):
|
|
|
285
302
|
def from_vsrc(
|
|
286
303
|
cls,
|
|
287
304
|
data: VirtualSourceConfig,
|
|
305
|
+
dtype_in: EnergyDType = EnergyDType.ivsample,
|
|
288
306
|
*,
|
|
289
307
|
log_intermediate_node: bool = False,
|
|
290
308
|
) -> Self:
|
|
291
309
|
states = data.calc_internal_states()
|
|
292
310
|
return cls(
|
|
293
311
|
# General
|
|
294
|
-
converter_mode=data.calc_converter_mode(
|
|
312
|
+
converter_mode=data.calc_converter_mode(
|
|
313
|
+
dtype_in, log_intermediate_node=log_intermediate_node
|
|
314
|
+
),
|
|
295
315
|
interval_startup_delay_drain_n=round(
|
|
296
316
|
data.interval_startup_delay_drain_ms * samplerate_sps_default * 1e-3
|
|
297
317
|
),
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# General Config
|
|
12
12
|
enable_boost: false # if false -> v_intermediate = v_input, output-switch-hysteresis is still usable
|
|
13
13
|
enable_buck: false # if false -> v_output = v_intermediate
|
|
14
|
+
enable_feedback_to_hrv: false # src can control a cv-harvester for ivcurve
|
|
14
15
|
|
|
15
16
|
interval_startup_delay_drain_ms: 0
|
|
16
17
|
|
|
@@ -26,18 +27,21 @@
|
|
|
26
27
|
V_intermediate_init_mV: 3000 # allow a proper / fast startup
|
|
27
28
|
I_intermediate_leak_nA: 0.0
|
|
28
29
|
|
|
30
|
+
# Output-Switch with comparator and hysteresis
|
|
29
31
|
V_intermediate_enable_threshold_mV: 1 # -> target gets connected (hysteresis-combo with next value)
|
|
30
32
|
V_intermediate_disable_threshold_mV: 0 # -> target gets disconnected
|
|
31
33
|
interval_check_thresholds_ms: 0.0 # some BQs check every 64 ms if output should be disconnected
|
|
32
34
|
|
|
35
|
+
# Power-Good signal from comparator and hysteresis
|
|
33
36
|
V_pwr_good_enable_threshold_mV: 2800 # target is informed by pwr-good on output-pin (hysteresis) -> for intermediate voltage
|
|
34
37
|
V_pwr_good_disable_threshold_mV: 2200
|
|
35
38
|
immediate_pwr_good_signal: true # 1: activate instant schmitt-trigger, 0: stay in interval for checking thresholds
|
|
36
39
|
|
|
37
|
-
C_output_uF: 1.0 # final (always last) stage to compensate
|
|
40
|
+
C_output_uF: 1.0 # final (always last) stage to compensate transient current spikes when enabling power for target
|
|
38
41
|
|
|
39
42
|
# Extra
|
|
40
43
|
V_output_log_gpio_threshold_mV: 1400 # min voltage needed to enable recording changes in gpio-bank
|
|
44
|
+
# TODO: actually disable gpio below that
|
|
41
45
|
|
|
42
46
|
# Boost Converter
|
|
43
47
|
V_input_boost_threshold_mV: 0.0 # min input-voltage for the boost converter to work
|
|
@@ -81,6 +85,8 @@
|
|
|
81
85
|
id: 1010
|
|
82
86
|
name: direct
|
|
83
87
|
inherit_from: neutral
|
|
88
|
+
harvester:
|
|
89
|
+
name: direct # even disables harvesting of ivcurve
|
|
84
90
|
# Note: current input has no influence
|
|
85
91
|
|
|
86
92
|
- datatype: VirtualSourceConfig
|
|
@@ -90,7 +96,13 @@
|
|
|
90
96
|
description: Simple Converter based on diode and buffer capacitor
|
|
91
97
|
inherit_from: neutral
|
|
92
98
|
V_input_drop_mV: 300 # simulate input-diode
|
|
93
|
-
C_intermediate_uF:
|
|
99
|
+
C_intermediate_uF: 47 # primary storage-Cap
|
|
100
|
+
harvester:
|
|
101
|
+
name: cv20
|
|
102
|
+
enable_feedback_to_hrv: true # src can control a cv-harvester for ivcurve
|
|
103
|
+
V_intermediate_enable_threshold_mV: 2000
|
|
104
|
+
V_intermediate_disable_threshold_mV: 1800 # nRF draw ~0.5 mA below that point
|
|
105
|
+
# TODO: put switch-output into special nRF Version
|
|
94
106
|
|
|
95
107
|
- datatype: VirtualSourceConfig
|
|
96
108
|
parameters:
|
|
@@ -230,3 +242,5 @@
|
|
|
230
242
|
id: 1033
|
|
231
243
|
name: default
|
|
232
244
|
inherit_from: BQ25570s
|
|
245
|
+
|
|
246
|
+
# TODO: add some generic boost-converters with mppt_po, _voc and more
|
|
@@ -33,7 +33,8 @@ class Compression(str, Enum):
|
|
|
33
33
|
lzf = default = "lzf" # not native hdf5
|
|
34
34
|
gzip1 = gzip = 1 # higher compr & load
|
|
35
35
|
null = None
|
|
36
|
-
# NOTE:
|
|
36
|
+
# NOTE: lzf & external file-compression (xz or zstd) work better than gzip
|
|
37
|
+
# -> even with additional compression
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
compressions_allowed: list = [None, "lzf", 1]
|
shepherd_core/reader.py
CHANGED
|
@@ -76,7 +76,7 @@ class Reader:
|
|
|
76
76
|
|
|
77
77
|
if not hasattr(self, "samplerate_sps"):
|
|
78
78
|
self.samplerate_sps: int = samplerate_sps_default
|
|
79
|
-
self.sample_interval_ns: int =
|
|
79
|
+
self.sample_interval_ns: int = round(10**9 // self.samplerate_sps)
|
|
80
80
|
self.sample_interval_s: float = 1 / self.samplerate_sps
|
|
81
81
|
|
|
82
82
|
self.max_elements: int = 40 * self.samplerate_sps
|
|
@@ -177,8 +177,8 @@ class Reader:
|
|
|
177
177
|
# this assumes iso-chronous sampling
|
|
178
178
|
duration_s = self._cal.time.raw_to_si(duration_raw)
|
|
179
179
|
self.sample_interval_s = duration_s / sample_count
|
|
180
|
-
self.sample_interval_ns =
|
|
181
|
-
self.samplerate_sps = max(
|
|
180
|
+
self.sample_interval_ns = round(10**9 * self.sample_interval_s)
|
|
181
|
+
self.samplerate_sps = max(round((sample_count - 1) / duration_s), 1)
|
|
182
182
|
self.runtime_s = round(self.ds_voltage.shape[0] / self.samplerate_sps, 1)
|
|
183
183
|
self.buffers_n = int(self.ds_voltage.shape[0] // self.samples_per_buffer)
|
|
184
184
|
if isinstance(self.file_path, Path):
|
shepherd_core/version.py
CHANGED
|
@@ -9,8 +9,10 @@ TODO: add more targets
|
|
|
9
9
|
- riotee
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import math
|
|
12
13
|
from abc import ABC
|
|
13
14
|
from abc import abstractmethod
|
|
15
|
+
from contextlib import suppress
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class TargetABC(ABC):
|
|
@@ -22,41 +24,118 @@ class TargetABC(ABC):
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class ResistiveTarget(TargetABC):
|
|
25
|
-
"""Predictable target
|
|
27
|
+
"""Predictable target with linear behavior."""
|
|
26
28
|
|
|
27
|
-
def __init__(self,
|
|
28
|
-
if
|
|
29
|
+
def __init__(self, R_Ohm: float, *, controlled: bool = False) -> None:
|
|
30
|
+
if R_Ohm <= 1e-3:
|
|
29
31
|
raise ValueError("Resistance must be greater than 1 mOhm.")
|
|
30
|
-
self.
|
|
32
|
+
self.R_kOhm = 1e-3 * R_Ohm
|
|
31
33
|
self.ctrl = controlled
|
|
32
34
|
|
|
33
35
|
def step(self, voltage_uV: int, *, pwr_good: bool) -> float:
|
|
34
36
|
if pwr_good or not self.ctrl:
|
|
35
|
-
return voltage_uV / self.
|
|
37
|
+
return voltage_uV / self.R_kOhm # = nA
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DiodeTarget(TargetABC):
|
|
42
|
+
"""Emulate a diode and current limiting resistor in series.
|
|
43
|
+
|
|
44
|
+
Good for modeling a debug-diode that burns energy.
|
|
45
|
+
It uses Shockley Diode Equation to estimate a model for diode
|
|
46
|
+
I_D = I_S * ( e ^ ( U_D / n*U_T ) - 1 )
|
|
47
|
+
|
|
48
|
+
diode of shepherd target:
|
|
49
|
+
d1 = DiodeTarget(V_forward_V = 2.0, I_forward_A = 20e-3, R_Ohm = 100)
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
V_forward_V: float,
|
|
56
|
+
I_forward_A: float,
|
|
57
|
+
R_Ohm: float,
|
|
58
|
+
*,
|
|
59
|
+
controlled: bool = False,
|
|
60
|
+
) -> None:
|
|
61
|
+
if R_Ohm <= 1e-3:
|
|
62
|
+
raise ValueError("Resistance must be greater than 1 mOhm.")
|
|
63
|
+
if V_forward_V <= 0.2:
|
|
64
|
+
raise ValueError("Forward-Voltage of diode must be greater than 200 mV.")
|
|
65
|
+
if I_forward_A <= 0:
|
|
66
|
+
raise ValueError("Forward-current of diode must be greater than 0 A.")
|
|
67
|
+
|
|
68
|
+
k = 1.380649e-23 # boltzmann
|
|
69
|
+
q = 1.6021766e-19 # elementary charge
|
|
70
|
+
TJ = 100 + 273.15 # junction temperature
|
|
71
|
+
|
|
72
|
+
V_T = k * TJ / q # thermal voltage
|
|
73
|
+
n = 2 # ideality factor
|
|
74
|
+
self.c1 = V_T * n
|
|
75
|
+
# NOTE: math.expm1(x) = math.exp(x) - 1 = e^x -1
|
|
76
|
+
self.I_S = I_forward_A / math.expm1(V_forward_V / self.c1) # scale current
|
|
77
|
+
self.R_Ohm = R_Ohm
|
|
78
|
+
self.ctrl = controlled
|
|
79
|
+
|
|
80
|
+
def step(self, voltage_uV: int, *, pwr_good: bool) -> float:
|
|
81
|
+
if pwr_good or not self.ctrl:
|
|
82
|
+
V_CC = voltage_uV * 1e-6
|
|
83
|
+
V_D = V_CC / 2
|
|
84
|
+
I_R = I_D = 0
|
|
85
|
+
# there is no direct formular, but this iteration converges fast
|
|
86
|
+
for _ in range(10):
|
|
87
|
+
# low voltages tend to produce log(x<0)=err
|
|
88
|
+
with suppress(ValueError):
|
|
89
|
+
V_D = self.c1 * math.log(1 + (V_CC - V_D) / (self.R_Ohm * self.I_S))
|
|
90
|
+
# both currents are positive and should be identical
|
|
91
|
+
I_R = max(0.0, (V_CC - V_D) / self.R_Ohm)
|
|
92
|
+
I_D = max(0.0, self.I_S * math.expm1(V_D / self.c1))
|
|
93
|
+
with suppress(ZeroDivisionError):
|
|
94
|
+
if abs(I_R / I_D - 1) < 1e-6:
|
|
95
|
+
break
|
|
96
|
+
# take mean of both currents and determine a new V_D
|
|
97
|
+
V_D = V_CC - self.R_Ohm * (I_R + I_D) / 2
|
|
98
|
+
return 1e9 * (I_R + I_D) / 2 # = nA
|
|
36
99
|
return 0
|
|
37
100
|
|
|
38
101
|
|
|
39
102
|
class ConstantCurrentTarget(TargetABC):
|
|
40
|
-
"""Recreate simple MCU without integrated regulator."""
|
|
103
|
+
"""Recreate simple MCU without integrated regulator, i.e. msp430."""
|
|
41
104
|
|
|
42
|
-
def __init__(self,
|
|
43
|
-
if
|
|
105
|
+
def __init__(self, I_active_A: float, I_sleep_A: float = 0) -> None:
|
|
106
|
+
if I_active_A <= 0 or I_sleep_A < 0:
|
|
44
107
|
raise ValueError("Current must be greater than 0.")
|
|
45
|
-
self.
|
|
46
|
-
self.
|
|
108
|
+
self.I_active_nA = 1e9 * I_active_A
|
|
109
|
+
self.I_sleep_nA = 1e9 * I_sleep_A
|
|
47
110
|
|
|
48
111
|
def step(self, voltage_uV: int, *, pwr_good: bool) -> float: # noqa: ARG002
|
|
49
|
-
return self.
|
|
112
|
+
return self.I_active_nA if pwr_good else self.I_sleep_nA
|
|
50
113
|
|
|
51
114
|
|
|
52
115
|
class ConstantPowerTarget(TargetABC):
|
|
53
|
-
"""Recreate MCU with integrated regulator."""
|
|
116
|
+
"""Recreate MCU with integrated regulator, i.e. nRF52."""
|
|
54
117
|
|
|
55
|
-
def __init__(self,
|
|
56
|
-
if
|
|
118
|
+
def __init__(self, P_active_W: float, P_sleep_W: float = 0) -> None:
|
|
119
|
+
if P_active_W <= 0 or P_sleep_W < 0:
|
|
57
120
|
raise ValueError("Power must be greater than 0.")
|
|
58
|
-
self.
|
|
59
|
-
self.
|
|
121
|
+
self.P_active_fW = 1e15 * P_active_W
|
|
122
|
+
self.P_sleep_fW = 1e15 * P_sleep_W
|
|
60
123
|
|
|
61
124
|
def step(self, voltage_uV: int, *, pwr_good: bool) -> float:
|
|
62
|
-
return (self.
|
|
125
|
+
return (self.P_active_fW if pwr_good else self.P_sleep_fW) / voltage_uV # = nA
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# exemplary instantiations
|
|
129
|
+
|
|
130
|
+
diode_target_burn = DiodeTarget(V_forward_V=2.0, I_forward_A=20e-3, R_Ohm=100)
|
|
131
|
+
mcu_msp430fr = ConstantCurrentTarget(I_active_A=16 * 118e-6, I_sleep_A=350e-9)
|
|
132
|
+
mcu_msp_deep_sleep = ConstantCurrentTarget(45e-9, 45e-9)
|
|
133
|
+
# TODO: writing FRAM
|
|
134
|
+
mcu_nrf52840 = ConstantPowerTarget(P_active_W=3 * 3e-3, P_sleep_W=3 * 0.97e-6)
|
|
135
|
+
mcu_nrf_tx_8dBm = ConstantPowerTarget(P_active_W=3.0 * 16.40e-3)
|
|
136
|
+
mcu_nrf_tx_0dBm = ConstantPowerTarget(P_active_W=3.0 * 6.40e-3)
|
|
137
|
+
mcu_nrf_rx = ConstantPowerTarget(P_active_W=3.0 * 6.26e-3)
|
|
138
|
+
# data based on PS1.10
|
|
139
|
+
# - TX 8 dBm -> 6.31 mW -> DS shows 16.40 mA @ 3V (DC/DC)
|
|
140
|
+
# - TX 0 dBm -> 1 mW -> 6.40 mA
|
|
141
|
+
# - RX -> 6.26 mA
|
|
@@ -43,21 +43,18 @@ class PruCalibration:
|
|
|
43
43
|
self.negative_residue_nA = 0
|
|
44
44
|
else:
|
|
45
45
|
self.negative_residue_nA = self.negative_residue_nA - I_nA
|
|
46
|
-
|
|
47
|
-
self.negative_residue_nA = self.RESIDUE_MAX_nA
|
|
46
|
+
self.negative_residue_nA = min(self.negative_residue_nA, self.RESIDUE_MAX_nA)
|
|
48
47
|
I_nA = 0
|
|
49
48
|
return I_nA
|
|
50
49
|
|
|
51
50
|
@staticmethod
|
|
52
|
-
def conv_adc_raw_to_uV(voltage_raw: int) ->
|
|
51
|
+
def conv_adc_raw_to_uV(voltage_raw: int) -> None:
|
|
53
52
|
msg = f"This Fn should not been used (val={voltage_raw})"
|
|
54
53
|
raise RuntimeError(msg)
|
|
55
54
|
|
|
56
55
|
def conv_uV_to_dac_raw(self, voltage_uV: float) -> int:
|
|
57
56
|
dac_raw = self.cal.dac_V_A.si_to_raw(float(voltage_uV) / (10**6))
|
|
58
|
-
|
|
59
|
-
dac_raw = (2**16) - 1
|
|
60
|
-
return dac_raw
|
|
57
|
+
return min(dac_raw, (2**16) - 1)
|
|
61
58
|
|
|
62
59
|
|
|
63
60
|
class VirtualConverterModel:
|
|
@@ -85,6 +82,9 @@ class VirtualConverterModel:
|
|
|
85
82
|
self.enable_boost: bool = (int(self._cfg.converter_mode) & 0b0010) > 0
|
|
86
83
|
self.enable_buck: bool = (int(self._cfg.converter_mode) & 0b0100) > 0
|
|
87
84
|
self.enable_log_mid: bool = (int(self._cfg.converter_mode) & 0b1000) > 0
|
|
85
|
+
# back-channel to hrv
|
|
86
|
+
self.feedback_to_hrv: bool = (int(self._cfg.converter_mode) & 0b1_0000) > 0
|
|
87
|
+
self.V_input_request_uV: int = self._cfg.V_intermediate_init_uV
|
|
88
88
|
|
|
89
89
|
self.V_out_dac_uV: float = self._cfg.V_output_uV
|
|
90
90
|
self.V_out_dac_raw: int = self._cal.conv_uV_to_dac_raw(self._cfg.V_output_uV)
|
|
@@ -95,12 +95,13 @@ class VirtualConverterModel:
|
|
|
95
95
|
self.V_enable_output_threshold_uV: float = self._cfg.V_enable_output_threshold_uV
|
|
96
96
|
self.V_disable_output_threshold_uV: float = self._cfg.V_disable_output_threshold_uV
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
self.
|
|
98
|
+
self.V_enable_output_threshold_uV = max(
|
|
99
|
+
self.dV_enable_output_uV, self.V_enable_output_threshold_uV
|
|
100
|
+
)
|
|
100
101
|
|
|
101
102
|
# pulled from update_states_and_output() due to easier static init
|
|
102
103
|
self.sample_count: int = 0xFFFFFFF0
|
|
103
|
-
self.is_outputting: bool =
|
|
104
|
+
self.is_outputting: bool = False
|
|
104
105
|
self.vsource_skip_gpio_logging: bool = False
|
|
105
106
|
|
|
106
107
|
def calc_inp_power(self, input_voltage_uV: float, input_current_nA: float) -> int:
|
|
@@ -114,31 +115,40 @@ class VirtualConverterModel:
|
|
|
114
115
|
else:
|
|
115
116
|
input_voltage_uV = 0.0
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
input_voltage_uV = self._cfg.V_input_max_uV
|
|
118
|
+
input_voltage_uV = min(input_voltage_uV, self._cfg.V_input_max_uV)
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
input_current_nA = self._cfg.I_input_max_nA
|
|
120
|
+
input_current_nA = min(input_current_nA, self._cfg.I_input_max_nA)
|
|
122
121
|
|
|
123
122
|
self.V_input_uV = input_voltage_uV
|
|
124
123
|
|
|
125
124
|
if self.enable_boost:
|
|
126
125
|
if input_voltage_uV < self._cfg.V_input_boost_threshold_uV:
|
|
127
126
|
input_voltage_uV = 0.0
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if V_drop_uV > V_diff_uV:
|
|
127
|
+
# TODO: vdrop in case of v_input > v_storage (non-boost)
|
|
128
|
+
elif self.enable_storage:
|
|
129
|
+
# no boost, but cap, for ie. diode+cap (+resistor)
|
|
130
|
+
V_diff_uV = (
|
|
131
|
+
(input_voltage_uV - self.V_mid_uV) if (input_voltage_uV >= self.V_mid_uV) else 0
|
|
132
|
+
)
|
|
133
|
+
V_res_drop_uV = input_current_nA * self.R_input_kOhm
|
|
134
|
+
if V_res_drop_uV > V_diff_uV:
|
|
137
135
|
input_voltage_uV = self.V_mid_uV
|
|
138
136
|
else:
|
|
139
|
-
input_voltage_uV -=
|
|
137
|
+
input_voltage_uV -= V_res_drop_uV
|
|
138
|
+
|
|
139
|
+
# IF input==ivcurve request new CV
|
|
140
|
+
if self.feedback_to_hrv:
|
|
141
|
+
self.V_input_request_uV = self.V_mid_uV + V_res_drop_uV + self._cfg.V_input_drop_uV
|
|
142
|
+
elif input_voltage_uV < self.V_mid_uV:
|
|
143
|
+
# without feedback there is no usable energy here
|
|
144
|
+
input_voltage_uV = 0
|
|
140
145
|
else:
|
|
146
|
+
# direct connection
|
|
147
|
+
# modifying V_mid here is not clean, but simpler
|
|
148
|
+
# -> V_mid is needed in calc_out, before cap is updated
|
|
149
|
+
self.V_mid_uV = input_voltage_uV
|
|
141
150
|
input_voltage_uV = 0.0
|
|
151
|
+
# ⤷ input will not be evaluated
|
|
142
152
|
|
|
143
153
|
if self.enable_boost:
|
|
144
154
|
eta_inp = self.get_input_efficiency(input_voltage_uV, input_current_nA)
|
|
@@ -170,6 +180,7 @@ class VirtualConverterModel:
|
|
|
170
180
|
|
|
171
181
|
# TODO: add range-checks for add, sub Ops
|
|
172
182
|
def update_cap_storage(self) -> int:
|
|
183
|
+
# TODO: this calculation is wrong for everything beside boost-cnv
|
|
173
184
|
if self.enable_storage:
|
|
174
185
|
V_mid_prot_uV = max(1.0, self.V_mid_uV)
|
|
175
186
|
P_sum_fW = self.P_inp_fW - self.P_out_fW
|
|
@@ -177,34 +188,31 @@ class VirtualConverterModel:
|
|
|
177
188
|
dV_mid_uV = I_mid_nA * self.Constant_us_per_nF
|
|
178
189
|
self.V_mid_uV += dV_mid_uV
|
|
179
190
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (not self.enable_boost) and (self.P_inp_fW > 0.0) and (self.V_mid_uV > self.V_input_uV):
|
|
183
|
-
# TODO: obfuscated - no "direct connection"?
|
|
184
|
-
self.V_mid_uV = self.V_input_uV
|
|
185
|
-
elif self.V_mid_uV < 1:
|
|
186
|
-
self.V_mid_uV = 1
|
|
191
|
+
self.V_mid_uV = min(self.V_mid_uV, self._cfg.V_intermediate_max_uV)
|
|
192
|
+
self.V_mid_uV = max(self.V_mid_uV, 1)
|
|
187
193
|
return round(self.V_mid_uV) # Python-specific, added for easier testing
|
|
188
194
|
|
|
189
195
|
def update_states_and_output(self) -> int:
|
|
190
196
|
self.sample_count += 1
|
|
191
197
|
check_thresholds = self.sample_count >= self._cfg.interval_check_thresholds_n
|
|
198
|
+
V_mid_uV_now = self.V_mid_uV
|
|
199
|
+
# copy avoids not enabling pwr_good (due to large dV_enable_output_uV)
|
|
192
200
|
|
|
193
201
|
if check_thresholds:
|
|
194
202
|
self.sample_count = 0
|
|
195
203
|
if self.is_outputting:
|
|
196
|
-
if
|
|
204
|
+
if V_mid_uV_now < self.V_disable_output_threshold_uV:
|
|
197
205
|
self.is_outputting = False
|
|
198
|
-
elif
|
|
206
|
+
elif V_mid_uV_now >= self.V_enable_output_threshold_uV:
|
|
199
207
|
self.is_outputting = True
|
|
200
208
|
self.V_mid_uV -= self.dV_enable_output_uV
|
|
201
209
|
|
|
202
210
|
if check_thresholds or self._cfg.immediate_pwr_good_signal:
|
|
203
211
|
# generate power-good-signal
|
|
204
212
|
if self.power_good:
|
|
205
|
-
if
|
|
213
|
+
if V_mid_uV_now <= self._cfg.V_pwr_good_disable_threshold_uV:
|
|
206
214
|
self.power_good = False
|
|
207
|
-
elif
|
|
215
|
+
elif V_mid_uV_now >= self._cfg.V_pwr_good_enable_threshold_uV:
|
|
208
216
|
self.power_good = self.is_outputting
|
|
209
217
|
# set batok pin to state ... TODO?
|
|
210
218
|
|
|
@@ -270,7 +278,7 @@ class VirtualConverterModel:
|
|
|
270
278
|
def get_power_good(self) -> bool:
|
|
271
279
|
return self.power_good
|
|
272
280
|
|
|
273
|
-
def
|
|
281
|
+
def get_I_mid_out_nA(self) -> float:
|
|
274
282
|
return self.P_out_fW / self.V_mid_uV
|
|
275
283
|
|
|
276
284
|
def get_state_log_intermediate(self) -> bool:
|
|
@@ -19,6 +19,7 @@ Compromises:
|
|
|
19
19
|
from typing import Tuple
|
|
20
20
|
|
|
21
21
|
from ..data_models.content.virtual_harvester import HarvesterPRUConfig
|
|
22
|
+
from ..logger import logger
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class VirtualHarvesterModel:
|
|
@@ -35,12 +36,20 @@ class VirtualHarvesterModel:
|
|
|
35
36
|
|
|
36
37
|
# INIT global vars: shared states
|
|
37
38
|
self.voltage_set_uV: int = self._cfg.voltage_uV + 1
|
|
39
|
+
|
|
40
|
+
self.is_emu: bool = bool(self._cfg.hrv_mode & (2**0))
|
|
41
|
+
if not self.is_emu:
|
|
42
|
+
logger.warning(
|
|
43
|
+
"This VSrc-config is not meant for emulation-mode -> activate 'is_emu' flag."
|
|
44
|
+
)
|
|
45
|
+
|
|
38
46
|
if self._cfg.interval_n > 2 * self._cfg.window_size:
|
|
39
47
|
self.interval_step = self._cfg.interval_n - (2 * self._cfg.window_size)
|
|
40
48
|
else:
|
|
41
49
|
self.interval_step = 2**30
|
|
42
50
|
# ⤷ intake two ivcurves before overflow / reset
|
|
43
|
-
|
|
51
|
+
|
|
52
|
+
self.is_rising: bool = bool(self._cfg.hrv_mode & (2**1))
|
|
44
53
|
|
|
45
54
|
# PO-Relevant, iv & adc
|
|
46
55
|
self.volt_step_uV: int = self._cfg.voltage_step_uV
|
|
@@ -54,13 +63,16 @@ class VirtualHarvesterModel:
|
|
|
54
63
|
# globals for iv_cv
|
|
55
64
|
self.voltage_hold: int = 0
|
|
56
65
|
self.current_hold: int = 0
|
|
57
|
-
self.voltage_step_x4_uV: int = self._cfg.voltage_step_uV
|
|
66
|
+
self.voltage_step_x4_uV: int = 4 * self._cfg.voltage_step_uV
|
|
58
67
|
self.age_max: int = 2 * self._cfg.window_size
|
|
59
68
|
|
|
60
69
|
# INIT static vars: CV
|
|
61
70
|
self.voltage_last: int = 0
|
|
62
71
|
self.current_last: int = 0
|
|
63
72
|
self.compare_last: int = 0
|
|
73
|
+
self.lin_extrapolation: bool = bool(self._cfg.hrv_mode & (2**2))
|
|
74
|
+
self.current_delta: int = 0
|
|
75
|
+
self.voltage_delta: int = 0
|
|
64
76
|
|
|
65
77
|
# INIT static vars: VOC
|
|
66
78
|
self.age_now: int = 0
|
|
@@ -106,9 +118,28 @@ class VirtualHarvesterModel:
|
|
|
106
118
|
if distance_now < distance_last and distance_now < self.voltage_step_x4_uV:
|
|
107
119
|
self.voltage_hold = _voltage_uV
|
|
108
120
|
self.current_hold = _current_nA
|
|
121
|
+
self.current_delta = _current_nA - self.current_last
|
|
122
|
+
self.voltage_delta = _voltage_uV - self.voltage_last
|
|
123
|
+
# TODO: voltage_delta is static
|
|
109
124
|
elif distance_last < distance_now and distance_last < self.voltage_step_x4_uV:
|
|
110
125
|
self.voltage_hold = self.voltage_last
|
|
111
126
|
self.current_hold = self.current_last
|
|
127
|
+
self.current_delta = _current_nA - self.current_last
|
|
128
|
+
self.voltage_delta = _voltage_uV - self.voltage_last
|
|
129
|
+
elif self.lin_extrapolation:
|
|
130
|
+
# apply the proper delta if needed
|
|
131
|
+
if (self.voltage_hold < self.voltage_set_uV) == (self.voltage_delta > 0):
|
|
132
|
+
self.voltage_hold += self.voltage_delta
|
|
133
|
+
self.current_hold += self.current_delta
|
|
134
|
+
else:
|
|
135
|
+
if self.voltage_hold > self.voltage_delta:
|
|
136
|
+
self.voltage_hold -= self.voltage_delta
|
|
137
|
+
else:
|
|
138
|
+
self.voltage_hold = 0
|
|
139
|
+
if self.current_hold > self.current_delta:
|
|
140
|
+
self.current_hold -= self.current_delta
|
|
141
|
+
else:
|
|
142
|
+
self.current_hold = 0
|
|
112
143
|
|
|
113
144
|
self.voltage_last = _voltage_uV
|
|
114
145
|
self.current_last = _current_nA
|
|
@@ -139,8 +170,9 @@ class VirtualHarvesterModel:
|
|
|
139
170
|
|
|
140
171
|
_voltage_uV, _current_nA = self.ivcurve_2_cv(_voltage_uV, _current_nA)
|
|
141
172
|
if self.interval_step < self._cfg.duration_n:
|
|
173
|
+
self.voltage_set_uV = self.voc_now
|
|
174
|
+
elif self.interval_step == self._cfg.duration_n:
|
|
142
175
|
self.voltage_set_uV = int(self.voc_now * self._cfg.setpoint_n8 / 256)
|
|
143
|
-
_current_nA = 0
|
|
144
176
|
|
|
145
177
|
return _voltage_uV, _current_nA
|
|
146
178
|
|
|
@@ -194,7 +226,7 @@ class VirtualHarvesterModel:
|
|
|
194
226
|
|
|
195
227
|
power_fW = _voltage_uV * _current_nA
|
|
196
228
|
if (
|
|
197
|
-
(power_fW
|
|
229
|
+
(power_fW >= self.power_nxt)
|
|
198
230
|
and (_voltage_uV >= self._cfg.voltage_min_uV)
|
|
199
231
|
and (_voltage_uV <= self._cfg.voltage_max_uV)
|
|
200
232
|
):
|
|
@@ -39,7 +39,9 @@ class VirtualSourceModel:
|
|
|
39
39
|
|
|
40
40
|
self.cfg_src = VirtualSourceConfig() if vsrc is None else vsrc
|
|
41
41
|
cnv_config = ConverterPRUConfig.from_vsrc(
|
|
42
|
-
self.cfg_src,
|
|
42
|
+
self.cfg_src,
|
|
43
|
+
dtype_in=dtype_in,
|
|
44
|
+
log_intermediate_node=log_intermediate,
|
|
43
45
|
)
|
|
44
46
|
self.cnv: VirtualConverterModel = VirtualConverterModel(cnv_config, self._cal_pru)
|
|
45
47
|
|
|
@@ -80,4 +82,8 @@ class VirtualSourceModel:
|
|
|
80
82
|
self.W_inp_fWs += P_inp_fW
|
|
81
83
|
self.W_out_fWs += P_out_fW
|
|
82
84
|
|
|
85
|
+
# feedback path - important for boost-less circuits
|
|
86
|
+
if self.cnv.feedback_to_hrv:
|
|
87
|
+
self.hrv.voltage_set_uV = self.cnv.V_input_request_uV
|
|
88
|
+
|
|
83
89
|
return V_mid_uV if self.cnv.get_state_log_intermediate() else V_out_uV
|
|
@@ -11,12 +11,14 @@ from contextlib import ExitStack
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Optional
|
|
13
13
|
|
|
14
|
+
import numpy as np
|
|
14
15
|
from tqdm import tqdm
|
|
15
16
|
|
|
16
17
|
from .. import CalibrationEmulator
|
|
17
18
|
from .. import Reader
|
|
18
19
|
from .. import Writer
|
|
19
20
|
from ..data_models import VirtualSourceConfig
|
|
21
|
+
from ..logger import logger
|
|
20
22
|
from . import VirtualSourceModel
|
|
21
23
|
from .target_model import TargetABC
|
|
22
24
|
|
|
@@ -26,6 +28,8 @@ def simulate_source(
|
|
|
26
28
|
target: TargetABC,
|
|
27
29
|
path_input: Path,
|
|
28
30
|
path_output: Optional[Path] = None,
|
|
31
|
+
*,
|
|
32
|
+
monitor_internals: bool = False,
|
|
29
33
|
) -> float:
|
|
30
34
|
"""Simulate behavior of virtual source algorithms.
|
|
31
35
|
|
|
@@ -47,10 +51,25 @@ def simulate_source(
|
|
|
47
51
|
cal_out = file_out.get_calibration_data()
|
|
48
52
|
|
|
49
53
|
src = VirtualSourceModel(
|
|
50
|
-
config,
|
|
54
|
+
config,
|
|
55
|
+
cal_emu,
|
|
56
|
+
dtype_in=file_inp.get_datatype(),
|
|
57
|
+
log_intermediate=False,
|
|
58
|
+
window_size=file_inp.get_window_samples(),
|
|
51
59
|
)
|
|
52
60
|
i_out_nA = 0
|
|
53
61
|
e_out_Ws = 0.0
|
|
62
|
+
if monitor_internals and path_output:
|
|
63
|
+
stats_sample = 0
|
|
64
|
+
stats_internal = np.empty((round(file_inp.runtime_s * file_inp.samplerate_sps), 11))
|
|
65
|
+
try:
|
|
66
|
+
# keep dependencies low
|
|
67
|
+
from matplotlib import pyplot as plt
|
|
68
|
+
except ImportError:
|
|
69
|
+
logger.warning("Matplotlib not installed, plotting of internals disabled")
|
|
70
|
+
stats_internal = None
|
|
71
|
+
else:
|
|
72
|
+
stats_internal = None
|
|
54
73
|
|
|
55
74
|
for _t, v_inp, i_inp in tqdm(
|
|
56
75
|
file_inp.read_buffers(is_raw=True), total=file_inp.buffers_n, desc="Buffers", leave=False
|
|
@@ -66,7 +85,22 @@ def simulate_source(
|
|
|
66
85
|
)
|
|
67
86
|
i_out_nA = target.step(int(v_uV[_n]), pwr_good=src.cnv.get_power_good())
|
|
68
87
|
i_nA[_n] = i_out_nA
|
|
69
|
-
|
|
88
|
+
|
|
89
|
+
if stats_internal is not None:
|
|
90
|
+
stats_internal[stats_sample] = [
|
|
91
|
+
_t[_n] * 1e-9, # s
|
|
92
|
+
src.hrv.voltage_hold * 1e-6,
|
|
93
|
+
src.cnv.V_input_request_uV * 1e-6, # V
|
|
94
|
+
src.hrv.voltage_set_uV * 1e-6,
|
|
95
|
+
src.cnv.V_mid_uV * 1e-6,
|
|
96
|
+
src.hrv.current_hold * 1e-6, # mA
|
|
97
|
+
src.hrv.current_delta * 1e-6,
|
|
98
|
+
i_out_nA * 1e-6,
|
|
99
|
+
src.cnv.P_inp_fW * 1e-12, # mW
|
|
100
|
+
src.cnv.P_out_fW * 1e-12,
|
|
101
|
+
src.cnv.get_power_good(),
|
|
102
|
+
]
|
|
103
|
+
stats_sample += 1
|
|
70
104
|
|
|
71
105
|
e_out_Ws += (v_uV * i_nA).sum() * 1e-15 * file_inp.sample_interval_s
|
|
72
106
|
if path_output:
|
|
@@ -75,4 +109,36 @@ def simulate_source(
|
|
|
75
109
|
file_out.append_iv_data_raw(_t, v_out, i_out)
|
|
76
110
|
|
|
77
111
|
stack.close()
|
|
112
|
+
|
|
113
|
+
if stats_internal is not None:
|
|
114
|
+
stats_internal = stats_internal[:stats_sample, :]
|
|
115
|
+
fig, axs = plt.subplots(4, 1, sharex="all", figsize=(20, 4 * 6), layout="tight")
|
|
116
|
+
fig.suptitle(f"VSrc-Sim with {config.name}, Inp={path_input.name}, E={e_out_Ws} Ws")
|
|
117
|
+
axs[0].set_ylabel("Voltages [V]")
|
|
118
|
+
axs[0].plot(stats_internal[:, 0], stats_internal[:, 1:5])
|
|
119
|
+
axs[0].legend(["V_cv_hold", "V_inp_Req", "V_cv_set", "V_cap"], loc="upper right")
|
|
120
|
+
|
|
121
|
+
axs[1].set_ylabel("Current [mA]")
|
|
122
|
+
axs[1].plot(stats_internal[:, 0], stats_internal[:, 5:8])
|
|
123
|
+
axs[1].legend(["C_cv_hold", "C_cv_delta", "C_out"], loc="upper right")
|
|
124
|
+
|
|
125
|
+
axs[2].set_ylabel("Power [mW]")
|
|
126
|
+
axs[2].plot(stats_internal[:, 0:1], stats_internal[:, 8:10])
|
|
127
|
+
axs[2].legend(["P_inp", "P_out"], loc="upper right")
|
|
128
|
+
|
|
129
|
+
axs[3].set_ylabel("PwrGood [n]")
|
|
130
|
+
axs[3].plot(stats_internal[:, 0], stats_internal[:, 10])
|
|
131
|
+
axs[3].legend(["PwrGood"], loc="upper right")
|
|
132
|
+
|
|
133
|
+
axs[3].set_xlabel("Runtime [s]")
|
|
134
|
+
|
|
135
|
+
for ax in axs:
|
|
136
|
+
# deactivates offset-creation for ax-ticks
|
|
137
|
+
ax.get_yaxis().get_major_formatter().set_useOffset(False)
|
|
138
|
+
ax.get_xaxis().get_major_formatter().set_useOffset(False)
|
|
139
|
+
|
|
140
|
+
plt.savefig(path_output.with_suffix(".png"))
|
|
141
|
+
plt.close(fig)
|
|
142
|
+
plt.clf()
|
|
143
|
+
|
|
78
144
|
return e_out_Ws
|
shepherd_core/writer.py
CHANGED
|
@@ -94,7 +94,7 @@ class Writer(Reader):
|
|
|
94
94
|
|
|
95
95
|
comp_default: int = 1
|
|
96
96
|
mode_default: str = "harvester"
|
|
97
|
-
datatype_default:
|
|
97
|
+
datatype_default: EnergyDType = EnergyDType.ivsample
|
|
98
98
|
|
|
99
99
|
_chunk_shape: tuple = (Reader.samples_per_buffer,)
|
|
100
100
|
|
|
@@ -137,79 +137,72 @@ class Writer(Reader):
|
|
|
137
137
|
self.file_path.name,
|
|
138
138
|
)
|
|
139
139
|
|
|
140
|
+
# open file
|
|
141
|
+
if self._modify:
|
|
142
|
+
self.h5file = h5py.File(self.file_path, "r+") # = rw
|
|
143
|
+
else:
|
|
144
|
+
if not self.file_path.parent.exists():
|
|
145
|
+
self.file_path.parent.mkdir(parents=True)
|
|
146
|
+
self.h5file = h5py.File(self.file_path, "w")
|
|
147
|
+
# ⤷ write, truncate if exist
|
|
148
|
+
self._create_skeleton()
|
|
149
|
+
|
|
150
|
+
# Handle Mode
|
|
140
151
|
if isinstance(mode, str) and mode not in self.mode_dtype_dict:
|
|
141
152
|
msg = f"Can't handle mode '{mode}' (choose one of {self.mode_dtype_dict})"
|
|
142
153
|
raise ValueError(msg)
|
|
143
154
|
|
|
144
|
-
|
|
155
|
+
if mode is not None:
|
|
156
|
+
self.h5file.attrs["mode"] = mode
|
|
157
|
+
if "mode" not in self.h5file.attrs:
|
|
158
|
+
self.h5file.attrs["mode"] = self.mode_default
|
|
159
|
+
|
|
160
|
+
_dtypes = self.mode_dtype_dict[self.get_mode()]
|
|
161
|
+
|
|
162
|
+
# Handle Datatype
|
|
145
163
|
if isinstance(datatype, str):
|
|
146
164
|
datatype = EnergyDType[datatype]
|
|
147
165
|
if isinstance(datatype, EnergyDType) and datatype not in _dtypes:
|
|
148
166
|
msg = f"Can't handle value '{datatype}' of datatype (choose one of {_dtypes})"
|
|
149
167
|
raise ValueError(msg)
|
|
150
168
|
|
|
151
|
-
if
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
self.
|
|
158
|
-
if not hasattr(self, "_datatype"):
|
|
159
|
-
self._datatype = self.datatype_default
|
|
160
|
-
if window_samples:
|
|
161
|
-
self._window_samples = window_samples
|
|
162
|
-
if not hasattr(self, "_window_samples"):
|
|
163
|
-
self._window_samples = 0
|
|
164
|
-
else:
|
|
165
|
-
self._mode = mode if isinstance(mode, str) else self.mode_default
|
|
166
|
-
self._datatype = (
|
|
167
|
-
datatype if isinstance(datatype, EnergyDType) else self.datatype_default
|
|
169
|
+
if isinstance(datatype, EnergyDType):
|
|
170
|
+
self.h5file["data"].attrs["datatype"] = datatype.name
|
|
171
|
+
if "datatype" not in self.h5file["data"].attrs:
|
|
172
|
+
self.h5file["data"].attrs["datatype"] = self.datatype_default.name
|
|
173
|
+
if self.get_datatype() not in _dtypes:
|
|
174
|
+
msg = (
|
|
175
|
+
f"Can't handle value '{self.get_datatype()}' of datatype (choose one of {_dtypes})"
|
|
168
176
|
)
|
|
169
|
-
|
|
177
|
+
raise ValueError(msg)
|
|
170
178
|
|
|
179
|
+
# Handle Window_samples
|
|
180
|
+
if window_samples is not None:
|
|
181
|
+
self.h5file["data"].attrs["window_samples"] = window_samples
|
|
182
|
+
if "window_samples" not in self.h5file["data"].attrs:
|
|
183
|
+
self.h5file["data"].attrs["window_samples"] = 0
|
|
184
|
+
|
|
185
|
+
if datatype == EnergyDType.ivcurve and self.get_window_samples() < 1:
|
|
186
|
+
raise ValueError("Window Size argument needed for ivcurve-Datatype")
|
|
187
|
+
|
|
188
|
+
# Handle Cal
|
|
171
189
|
if isinstance(cal_data, (CalEmu, CalHrv)):
|
|
172
|
-
|
|
173
|
-
elif isinstance(cal_data, CalSeries):
|
|
174
|
-
self._cal = cal_data
|
|
175
|
-
else:
|
|
176
|
-
self._cal = CalSeries()
|
|
190
|
+
cal_data = CalSeries.from_cal(cal_data)
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
if isinstance(cal_data, CalSeries):
|
|
193
|
+
for ds, param in product(["current", "voltage", "time"], ["gain", "offset"]):
|
|
194
|
+
self.h5file["data"][ds].attrs[param] = cal_data[ds][param]
|
|
181
195
|
else:
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
# check if there are unset cal-values and set them to default
|
|
197
|
+
cal_data = CalSeries()
|
|
198
|
+
for ds, param in product(["current", "voltage", "time"], ["gain", "offset"]):
|
|
199
|
+
if param not in self.h5file["data"][ds].attrs:
|
|
200
|
+
self.h5file["data"][ds].attrs[param] = cal_data[ds][param]
|
|
187
201
|
|
|
188
202
|
# show key parameters for h5-performance
|
|
189
203
|
settings = list(self.h5file.id.get_access_plist().get_cache())
|
|
190
204
|
self._logger.debug("H5Py Cache_setting=%s (_mdc, _nslots, _nbytes, _w0)", settings)
|
|
191
205
|
|
|
192
|
-
# Store the mode in order to allow user to differentiate harvesting vs emulation data
|
|
193
|
-
if isinstance(self._mode, str) and self._mode in self.mode_dtype_dict:
|
|
194
|
-
self.h5file.attrs["mode"] = self._mode
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
isinstance(self._datatype, EnergyDType)
|
|
198
|
-
and self._datatype in self.mode_dtype_dict[self.get_mode()]
|
|
199
|
-
):
|
|
200
|
-
self.h5file["data"].attrs["datatype"] = self._datatype.name
|
|
201
|
-
elif not self._modify:
|
|
202
|
-
self._logger.error("datatype invalid? '%s' not written", self._datatype)
|
|
203
|
-
|
|
204
|
-
if isinstance(self._window_samples, int):
|
|
205
|
-
self.h5file["data"].attrs["window_samples"] = self._window_samples
|
|
206
|
-
if datatype == EnergyDType.ivcurve and (self._window_samples in {None, 0}):
|
|
207
|
-
raise ValueError("Window Size argument needed for ivcurve-Datatype")
|
|
208
|
-
|
|
209
|
-
# include cal-data
|
|
210
|
-
for ds, param in product(["current", "voltage", "time"], ["gain", "offset"]):
|
|
211
|
-
self.h5file["data"][ds].attrs[param] = self._cal[ds][param]
|
|
212
|
-
|
|
213
206
|
super().__init__(file_path=None, verbose=verbose)
|
|
214
207
|
|
|
215
208
|
def __enter__(self) -> Self:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: shepherd_core
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.11.1
|
|
4
4
|
Summary: Programming- and CLI-Interface for the h5-dataformat of the Shepherd-Testbed
|
|
5
5
|
Author-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
6
6
|
Maintainer-email: Ingmar Splitt <ingmar.splitt@tu-dresden.de>
|
|
@@ -23,6 +23,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
27
|
Classifier: License :: OSI Approved :: MIT License
|
|
27
28
|
Classifier: Operating System :: OS Independent
|
|
28
29
|
Classifier: Natural Language :: English
|
|
@@ -119,11 +120,11 @@ These pydantic data-models are used throughout all shepherd interfaces. Users ca
|
|
|
119
120
|
|
|
120
121
|
## Compatibility
|
|
121
122
|
|
|
122
|
-
| OS |
|
|
123
|
-
|
|
124
|
-
| Ubuntu | 3.8 - 3.
|
|
125
|
-
| Windows | 3.8 - 3.
|
|
126
|
-
| MacOS | 3.8 - 3.
|
|
123
|
+
| OS | PyVersion | Comment |
|
|
124
|
+
|---------|------------|--------------------------------------------|
|
|
125
|
+
| Ubuntu | 3.8 - 3.13 | |
|
|
126
|
+
| Windows | 3.8 - 3.13 | no support for elf and hex-conversions yet |
|
|
127
|
+
| MacOS | 3.8 - 3.13 | hex-conversion missing |
|
|
127
128
|
|
|
128
129
|
Notes:
|
|
129
130
|
- hex-conversion needs a working and accessible objcopy
|
|
@@ -147,7 +148,7 @@ For bleeding-edge-features or dev-work it is possible to install directly from G
|
|
|
147
148
|
```Shell
|
|
148
149
|
pip install git+https://github.com/orgua/shepherd-datalib.git@dev#subdirectory=shepherd_core -U
|
|
149
150
|
# and on sheep with newer debian
|
|
150
|
-
pip install git+https://github.com/orgua/shepherd-datalib.git@dev#subdirectory=shepherd_core -U --break-system-packages
|
|
151
|
+
sudo pip install git+https://github.com/orgua/shepherd-datalib.git@dev#subdirectory=shepherd_core -U --break-system-packages
|
|
151
152
|
```
|
|
152
153
|
|
|
153
154
|
If you are working with ``.elf``-files (embedding into experiments) you make "objcopy" accessible to python. In Ubuntu, you can either install ``build-essential`` or ``binutils-$ARCH`` with arch being ``msp430`` or ``arm-none-eabi`` for the nRF52.
|
|
@@ -2,9 +2,9 @@ shepherd_core/__init__.py,sha256=QyqENyf508XfZQ4vDU5o6UL9rmIqkf8kzwgTF9XU1-Y,127
|
|
|
2
2
|
shepherd_core/calibration_hw_def.py,sha256=_nMzgNzSnYyqcLnVCGd4tfA2e0avUXbccjmNpFhiDgo,2830
|
|
3
3
|
shepherd_core/commons.py,sha256=vymKXWcy_1bz7ChzzEATUkJ4p3czCzjIdsSehVjJOY8,218
|
|
4
4
|
shepherd_core/logger.py,sha256=4Q4hTI-nccOZ1_A68fo4UEctfu3pJx3IeHfa9VuDDEo,1804
|
|
5
|
-
shepherd_core/reader.py,sha256=
|
|
6
|
-
shepherd_core/version.py,sha256=
|
|
7
|
-
shepherd_core/writer.py,sha256=
|
|
5
|
+
shepherd_core/reader.py,sha256=Sg7s5UfV01CejI3_nolG7qrSDUBenmt1WPo2BPJee8o,27058
|
|
6
|
+
shepherd_core/version.py,sha256=_cAEEjp9MalkLrnbibrPNp6zEr3U5KIC-gmZvPG5QqA,76
|
|
7
|
+
shepherd_core/writer.py,sha256=GMR-7vkOgpTNPoknBWsRsC7-b7iGMtT60-KtSKunNe8,14636
|
|
8
8
|
shepherd_core/data_models/__init__.py,sha256=IVjKbT2Ilz5bev325EvAuuhd9LfQgQ1u7qKo6dhVA2k,1866
|
|
9
9
|
shepherd_core/data_models/readme.md,sha256=1bdfEypY_0NMhXLxOPRnLAsFca0HuHdq7_01yEWxvUs,2470
|
|
10
10
|
shepherd_core/data_models/virtual_source_doc.txt,sha256=KizMcfGKj7BnHIbaJHT7KeTF01SV__UXv01qV_DGHSs,6057
|
|
@@ -17,20 +17,20 @@ shepherd_core/data_models/base/timezone.py,sha256=2T6E46hJ1DAvmqKfu6uIgCK3RSoAKj
|
|
|
17
17
|
shepherd_core/data_models/base/wrapper.py,sha256=Izp17HFCKNAS3TnWcPn3MM9fWdc3A-F7eDyAsYlyWCw,755
|
|
18
18
|
shepherd_core/data_models/content/__init__.py,sha256=wVa5lw6bS-fBgeo-SWydg6rw8AsScxqNgDo81dzteaE,537
|
|
19
19
|
shepherd_core/data_models/content/_external_fixtures.yaml,sha256=0CH7YSWT_hzL-jcg4JjgN9ryQOzbS8S66_pd6GbMnHw,12259
|
|
20
|
-
shepherd_core/data_models/content/energy_environment.py,sha256=
|
|
20
|
+
shepherd_core/data_models/content/energy_environment.py,sha256=WuXMkKqnibGzM2WeW1_m2DAsc0fDqE9CkBYYPSw-7eA,1540
|
|
21
21
|
shepherd_core/data_models/content/energy_environment_fixture.yaml,sha256=UBXTdGT7MK98zx5w_RBCu-f9uNCKxRgiFBQFbmDUxPc,1301
|
|
22
22
|
shepherd_core/data_models/content/firmware.py,sha256=MyEiaP6bkOm7i_oihDXTxHC7ajc5aqiIDLn7mhap6YY,5722
|
|
23
23
|
shepherd_core/data_models/content/firmware_datatype.py,sha256=XPU9LOoT3h5qFOlE8WU0vAkw-vymNxzor9kVFyEqsWg,255
|
|
24
|
-
shepherd_core/data_models/content/virtual_harvester.py,sha256=
|
|
25
|
-
shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=
|
|
26
|
-
shepherd_core/data_models/content/virtual_source.py,sha256=
|
|
27
|
-
shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=
|
|
24
|
+
shepherd_core/data_models/content/virtual_harvester.py,sha256=MXmSJ_nRp1mSzxfTNk60o9h5Yrp2lFMbLphUVSnNeNc,9999
|
|
25
|
+
shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=LZe5ue1xYhXZwB3a32sva-L4uKhkQA5AtG9JzW4B2hQ,4564
|
|
26
|
+
shepherd_core/data_models/content/virtual_source.py,sha256=PPAphxEXvgMM7OVZ2dBkYAvJQkmj5Kb2BYFogVUs7B8,15354
|
|
27
|
+
shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=1o-31mGgn7eyCNidKoOUp9vZh3K4Al0kJgmz54Q2DAE,11191
|
|
28
28
|
shepherd_core/data_models/experiment/__init__.py,sha256=9TE9_aSnCNRhagsIWLTE8XkyjyMGB7kEGdswl-296v0,645
|
|
29
29
|
shepherd_core/data_models/experiment/experiment.py,sha256=wnn6T3czuh4rz6OSYtMltCTbRpPX55TLVAtQcKO7Uhg,4044
|
|
30
30
|
shepherd_core/data_models/experiment/observer_features.py,sha256=qxnb7anuQz9ZW5IUlPdUXYPIl5U7O9uXkJqZtMnAb0Y,5156
|
|
31
31
|
shepherd_core/data_models/experiment/target_config.py,sha256=XIsjbbo7yn_A4q3GMxWbiNzEGA0Kk5gH7-XfQQ7Kg0E,3674
|
|
32
32
|
shepherd_core/data_models/task/__init__.py,sha256=rZLbgqX-dTWY4026-bqW-IWVHbA6C_xP9y0aeRze8FY,3374
|
|
33
|
-
shepherd_core/data_models/task/emulation.py,sha256=
|
|
33
|
+
shepherd_core/data_models/task/emulation.py,sha256=tLb5auHOgdoG-e4hFljAYT49z7lMEaiimOy4UVZONi4,6440
|
|
34
34
|
shepherd_core/data_models/task/firmware_mod.py,sha256=Rw_TA1ykQ7abUd_U0snqZlpZyrS8Nx6f4BEax1Xnji0,2818
|
|
35
35
|
shepherd_core/data_models/task/harvest.py,sha256=HHnqWwRsJupaZJxuohs7NrK6VaDyoRzGOaG2h9y3s1Y,3360
|
|
36
36
|
shepherd_core/data_models/task/observer_tasks.py,sha256=XlH_-EGRrdodTn0c2pjGvpcauc0a9NOnLhysKw8iRwk,3511
|
|
@@ -67,14 +67,14 @@ shepherd_core/testbed_client/client_web.py,sha256=iMh5T91152uugbFsqr2vvxLser0KIo
|
|
|
67
67
|
shepherd_core/testbed_client/fixtures.py,sha256=4Uk583R4r6I5IB78HxOn-9UNH3sbFha7OPEdcSXvMCU,9939
|
|
68
68
|
shepherd_core/testbed_client/user_model.py,sha256=5M3vWkAGBwdGDUYAanAjrZwpzMBlh3XLOVvNYWiLmms,2107
|
|
69
69
|
shepherd_core/vsource/__init__.py,sha256=GVB-FwuO2mvM15mGX9EQC1lbUmHMLmUEFGYkGmIngPM,771
|
|
70
|
-
shepherd_core/vsource/target_model.py,sha256=
|
|
71
|
-
shepherd_core/vsource/virtual_converter_model.py,sha256=
|
|
72
|
-
shepherd_core/vsource/virtual_harvester_model.py,sha256=
|
|
70
|
+
shepherd_core/vsource/target_model.py,sha256=LaB5ppi2-IIpIepDqDvOliR-BupzccJl44yRxjlF-ms,5113
|
|
71
|
+
shepherd_core/vsource/virtual_converter_model.py,sha256=3TyxphUMunoGhMda7AWCHZQU8pjRSvxB-9R8lfZFnok,11592
|
|
72
|
+
shepherd_core/vsource/virtual_harvester_model.py,sha256=GyA0uGl3r42t5c4roYtEaj22b0-b5DAHUr2e9DuNn-c,9765
|
|
73
73
|
shepherd_core/vsource/virtual_harvester_simulation.py,sha256=EiBrvmc6D2N7Z0DqFBWPdRJK6hR8Q3iQaj7EYP9pLA0,2405
|
|
74
|
-
shepherd_core/vsource/virtual_source_model.py,sha256
|
|
75
|
-
shepherd_core/vsource/virtual_source_simulation.py,sha256=
|
|
76
|
-
shepherd_core-2024.
|
|
77
|
-
shepherd_core-2024.
|
|
78
|
-
shepherd_core-2024.
|
|
79
|
-
shepherd_core-2024.
|
|
80
|
-
shepherd_core-2024.
|
|
74
|
+
shepherd_core/vsource/virtual_source_model.py,sha256=-JSYUfsnYlNo5RfPBhx2G33fo5AjSeFSf2O6unroyFw,2945
|
|
75
|
+
shepherd_core/vsource/virtual_source_simulation.py,sha256=k1v2zpNdJTqiO9uY8TXaq-IUKK6m5l-LEWebYva0skk,5088
|
|
76
|
+
shepherd_core-2024.11.1.dist-info/METADATA,sha256=TPLadz0PyPjY4RxradpDtestEq_6U2ILu9tVNIIG7q0,7818
|
|
77
|
+
shepherd_core-2024.11.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
78
|
+
shepherd_core-2024.11.1.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
|
|
79
|
+
shepherd_core-2024.11.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
80
|
+
shepherd_core-2024.11.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|