shepherd-core 2024.7.3__py3-none-any.whl → 2024.8.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/calibration_hw_def.py +15 -12
- shepherd_core/data_models/base/calibration.py +23 -24
- shepherd_core/data_models/content/virtual_harvester.py +18 -11
- shepherd_core/data_models/content/virtual_harvester_fixture.yaml +10 -13
- shepherd_core/data_models/content/virtual_source.py +6 -4
- shepherd_core/data_models/content/virtual_source_fixture.yaml +10 -8
- shepherd_core/reader.py +9 -4
- shepherd_core/version.py +1 -1
- shepherd_core/vsource/virtual_converter_model.py +21 -6
- shepherd_core/vsource/virtual_harvester_model.py +18 -8
- shepherd_core/writer.py +13 -3
- {shepherd_core-2024.7.3.dist-info → shepherd_core-2024.8.1.dist-info}/METADATA +9 -7
- {shepherd_core-2024.7.3.dist-info → shepherd_core-2024.8.1.dist-info}/RECORD +16 -16
- {shepherd_core-2024.7.3.dist-info → shepherd_core-2024.8.1.dist-info}/WHEEL +1 -1
- {shepherd_core-2024.7.3.dist-info → shepherd_core-2024.8.1.dist-info}/top_level.txt +0 -0
- {shepherd_core-2024.7.3.dist-info → shepherd_core-2024.8.1.dist-info}/zip-safe +0 -0
|
@@ -32,45 +32,48 @@ RAW_MAX_ADC = 2**M_ADC - 1
|
|
|
32
32
|
RAW_MAX_DAC = 2**M_DAC - 1
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def adc_current_to_raw(current: float) -> int:
|
|
35
|
+
def adc_current_to_raw(current: float, *, limited: bool = True) -> int:
|
|
36
36
|
"""Convert back a current [A] to raw ADC value."""
|
|
37
37
|
# voltage on input of adc
|
|
38
38
|
val_adc = G_INST_AMP * R_SHT * current
|
|
39
39
|
# digital value according to ADC gain
|
|
40
40
|
val_raw = int(val_adc * (2**M_ADC) / (G_ADC_I * V_REF_ADC))
|
|
41
|
-
return min(max(val_raw, 0), 2**M_ADC - 1)
|
|
41
|
+
return min(max(val_raw, 0), 2**M_ADC - 1) if limited else val_raw
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def adc_raw_to_current(value: int) -> float:
|
|
44
|
+
def adc_raw_to_current(value: int, *, limited: bool = True) -> float:
|
|
45
45
|
"""Convert a raw ADC value to a current [A]."""
|
|
46
|
-
|
|
46
|
+
if limited:
|
|
47
|
+
value = min(max(value, 0), 2**M_ADC - 1)
|
|
47
48
|
# voltage on input of adc
|
|
48
49
|
val_adc = float(value) * (G_ADC_I * V_REF_ADC) / (2**M_ADC)
|
|
49
50
|
# current according to adc value
|
|
50
51
|
return val_adc / (R_SHT * G_INST_AMP)
|
|
51
52
|
|
|
52
53
|
|
|
53
|
-
def adc_voltage_to_raw(voltage: float) -> int:
|
|
54
|
+
def adc_voltage_to_raw(voltage: float, *, limited: bool = True) -> int:
|
|
54
55
|
"""Convert back a voltage [V] to raw ADC value."""
|
|
55
56
|
# digital value according to ADC gain
|
|
56
57
|
val_raw = int(voltage * (2**M_ADC) / (G_ADC_V * V_REF_ADC))
|
|
57
|
-
return min(max(val_raw, 0), 2**M_ADC - 1)
|
|
58
|
+
return min(max(val_raw, 0), 2**M_ADC - 1) if limited else val_raw
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def adc_raw_to_voltage(value: int) -> float:
|
|
61
|
+
def adc_raw_to_voltage(value: int, *, limited: bool = True) -> float:
|
|
61
62
|
"""Convert a raw ADC value to a voltage [V]."""
|
|
62
|
-
|
|
63
|
+
if limited:
|
|
64
|
+
value = min(max(value, 0), 2**M_ADC - 1)
|
|
63
65
|
# voltage according to ADC value
|
|
64
66
|
return float(value) * (G_ADC_V * V_REF_ADC) / (2**M_ADC)
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
def dac_raw_to_voltage(value: int) -> float:
|
|
69
|
+
def dac_raw_to_voltage(value: int, *, limited: bool = True) -> float:
|
|
68
70
|
"""Convert back a raw DAC value to a voltage [V]."""
|
|
69
|
-
|
|
71
|
+
if limited:
|
|
72
|
+
value = min(max(value, 0), 2**M_DAC - 1)
|
|
70
73
|
return float(value) * (V_REF_DAC * G_DAC) / (2**M_DAC)
|
|
71
74
|
|
|
72
75
|
|
|
73
|
-
def dac_voltage_to_raw(voltage: float) -> int:
|
|
76
|
+
def dac_voltage_to_raw(voltage: float, *, limited: bool = True) -> int:
|
|
74
77
|
"""Convert a voltage [V] to raw DAC value."""
|
|
75
78
|
val_raw = int(voltage * (2**M_DAC) / (V_REF_DAC * G_DAC))
|
|
76
|
-
return min(max(val_raw, 0), 2**M_DAC - 1)
|
|
79
|
+
return min(max(val_raw, 0), 2**M_DAC - 1) if limited else val_raw
|
|
@@ -51,6 +51,7 @@ class CalibrationPair(ShpModel):
|
|
|
51
51
|
|
|
52
52
|
gain: PositiveFloat
|
|
53
53
|
offset: float = 0
|
|
54
|
+
unit: Optional[str] = None # TODO: add units when used
|
|
54
55
|
|
|
55
56
|
def raw_to_si(self, values_raw: Calc_t, *, allow_negative: bool = True) -> Calc_t:
|
|
56
57
|
"""Convert between physical units and raw unsigned integers."""
|
|
@@ -77,14 +78,11 @@ class CalibrationPair(ShpModel):
|
|
|
77
78
|
return values_raw
|
|
78
79
|
|
|
79
80
|
@classmethod
|
|
80
|
-
def from_fn(cls, fn: Callable) -> Self:
|
|
81
|
+
def from_fn(cls, fn: Callable, unit: Optional[str] = None) -> Self:
|
|
81
82
|
"""Probe linear function to determine scaling values."""
|
|
82
|
-
offset = fn(0)
|
|
83
|
-
gain_inv = fn(1.0) - offset
|
|
84
|
-
return cls(
|
|
85
|
-
gain=1.0 / float(gain_inv),
|
|
86
|
-
offset=-float(offset) / gain_inv,
|
|
87
|
-
)
|
|
83
|
+
offset = fn(0, limited=False)
|
|
84
|
+
gain_inv = fn(1.0, limited=False) - offset
|
|
85
|
+
return cls(gain=1.0 / float(gain_inv), offset=-float(offset) / gain_inv, unit=unit)
|
|
88
86
|
|
|
89
87
|
|
|
90
88
|
cal_hrv_legacy = { # legacy translator
|
|
@@ -98,10 +96,10 @@ cal_hrv_legacy = { # legacy translator
|
|
|
98
96
|
class CalibrationHarvester(ShpModel):
|
|
99
97
|
"""Container for all calibration-pairs for that device."""
|
|
100
98
|
|
|
101
|
-
dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
102
|
-
dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
103
|
-
adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw)
|
|
104
|
-
adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
|
|
99
|
+
dac_V_Hrv: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
|
|
100
|
+
dac_V_Sim: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
|
|
101
|
+
adc_V_Sense: CalibrationPair = CalibrationPair.from_fn(adc_voltage_to_raw, unit="V")
|
|
102
|
+
adc_C_Hrv: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
|
|
105
103
|
|
|
106
104
|
def export_for_sysfs(self) -> dict:
|
|
107
105
|
"""Convert and write the essential data.
|
|
@@ -142,10 +140,10 @@ class CalibrationEmulator(ShpModel):
|
|
|
142
140
|
Differentiates between both target-ports A/B.
|
|
143
141
|
"""
|
|
144
142
|
|
|
145
|
-
dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
146
|
-
dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw)
|
|
147
|
-
adc_C_A: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
|
|
148
|
-
adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw)
|
|
143
|
+
dac_V_A: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
|
|
144
|
+
dac_V_B: CalibrationPair = CalibrationPair.from_fn(dac_voltage_to_raw, unit="V")
|
|
145
|
+
adc_C_A: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
|
|
146
|
+
adc_C_B: CalibrationPair = CalibrationPair.from_fn(adc_current_to_raw, unit="A")
|
|
149
147
|
|
|
150
148
|
def export_for_sysfs(self) -> dict:
|
|
151
149
|
"""Convert and write the essential data.
|
|
@@ -232,10 +230,11 @@ class CalibrationCape(ShpModel):
|
|
|
232
230
|
|
|
233
231
|
"""
|
|
234
232
|
dv = cls().model_dump(include={"harvester", "emulator"})
|
|
235
|
-
|
|
236
|
-
|
|
233
|
+
lw1 = list(dict_generator(dv))
|
|
234
|
+
lw2 = [elem for elem in lw1 if isinstance(elem[-1], float)]
|
|
235
|
+
values = struct.unpack(">" + len(lw2) * "d", data)
|
|
237
236
|
# ⤷ X => double float, big endian
|
|
238
|
-
for _i, walk in enumerate(
|
|
237
|
+
for _i, walk in enumerate(lw2):
|
|
239
238
|
# hardcoded fixed depth ... bad but easy
|
|
240
239
|
dv[walk[0]][walk[1]][walk[2]] = float(values[_i])
|
|
241
240
|
dv["cape"] = cape
|
|
@@ -251,18 +250,18 @@ class CalibrationCape(ShpModel):
|
|
|
251
250
|
|
|
252
251
|
"""
|
|
253
252
|
lw = list(dict_generator(self.model_dump(include={"harvester", "emulator"})))
|
|
254
|
-
values = [walk[-1] for walk in lw]
|
|
255
|
-
return struct.pack(">" + len(
|
|
253
|
+
values = [walk[-1] for walk in lw if isinstance(walk[-1], float)]
|
|
254
|
+
return struct.pack(">" + len(values) * "d", *values)
|
|
256
255
|
|
|
257
256
|
|
|
258
257
|
class CalibrationSeries(ShpModel):
|
|
259
258
|
"""Cal-Data for a typical recording of a testbed experiment."""
|
|
260
259
|
|
|
261
|
-
voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9)
|
|
260
|
+
voltage: CalibrationPair = CalibrationPair(gain=3 * 1e-9, unit="V")
|
|
262
261
|
# ⤷ default allows 0 - 12 V in 3 nV-Steps
|
|
263
|
-
current: CalibrationPair = CalibrationPair(gain=250 * 1e-12)
|
|
262
|
+
current: CalibrationPair = CalibrationPair(gain=250 * 1e-12, unit="A")
|
|
264
263
|
# ⤷ default allows 0 - 1 A in 250 pA - Steps
|
|
265
|
-
time: CalibrationPair = CalibrationPair(gain=1e-9)
|
|
264
|
+
time: CalibrationPair = CalibrationPair(gain=1e-9, unit="s")
|
|
266
265
|
# ⤷ default = nanoseconds
|
|
267
266
|
|
|
268
267
|
@classmethod
|
|
@@ -274,7 +273,7 @@ class CalibrationSeries(ShpModel):
|
|
|
274
273
|
emu_port_a: bool = True,
|
|
275
274
|
) -> Self:
|
|
276
275
|
if isinstance(cal, CalibrationHarvester):
|
|
277
|
-
return cls(voltage=cal.adc_V_Sense, current=cal.
|
|
276
|
+
return cls(voltage=cal.adc_V_Sense, current=cal.adc_C_Hrv)
|
|
278
277
|
if emu_port_a:
|
|
279
278
|
return cls(voltage=cal.dac_V_A, current=cal.adc_C_A)
|
|
280
279
|
return cls(voltage=cal.dac_V_B, current=cal.adc_C_B)
|
|
@@ -149,20 +149,27 @@ class VirtualHarvesterConfig(ContentModel, title="Config for the Harvester"):
|
|
|
149
149
|
|
|
150
150
|
def calc_window_size(
|
|
151
151
|
self,
|
|
152
|
-
dtype_in: Optional[EnergyDType] =
|
|
152
|
+
dtype_in: Optional[EnergyDType] = None,
|
|
153
153
|
*,
|
|
154
154
|
for_emu: bool,
|
|
155
155
|
) -> int:
|
|
156
|
-
if for_emu:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
raise NotImplementedError
|
|
156
|
+
if not for_emu:
|
|
157
|
+
# TODO: should be named 'for_ivcurve_recording'
|
|
158
|
+
# TODO: add extra variable to distinguish step_count
|
|
159
|
+
# and window_size (currently mixed together)
|
|
160
|
+
# only used by ivcurve algo (in ADC-Mode)
|
|
161
|
+
return self.samples_n
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
163
|
+
if dtype_in is None:
|
|
164
|
+
dtype_in = self.get_datatype()
|
|
165
|
+
|
|
166
|
+
if dtype_in == EnergyDType.ivcurve:
|
|
167
|
+
return self.samples_n * (1 + self.wait_cycles)
|
|
168
|
+
if dtype_in == EnergyDType.ivsample:
|
|
169
|
+
return 0
|
|
170
|
+
if dtype_in == EnergyDType.isc_voc:
|
|
171
|
+
return 2 * (1 + self.wait_cycles)
|
|
172
|
+
raise NotImplementedError
|
|
166
173
|
|
|
167
174
|
|
|
168
175
|
u32 = Annotated[int, Field(ge=0, lt=2**32)]
|
|
@@ -234,7 +241,7 @@ class HarvesterPRUConfig(ShpModel):
|
|
|
234
241
|
dtype_in = EnergyDType[dtype_in]
|
|
235
242
|
if for_emu and dtype_in not in {EnergyDType.ivsample, EnergyDType.ivcurve}:
|
|
236
243
|
raise NotImplementedError
|
|
237
|
-
|
|
244
|
+
|
|
238
245
|
interval_ms, duration_ms = data.calc_timings_ms(for_emu=for_emu)
|
|
239
246
|
return cls(
|
|
240
247
|
algorithm=data.calc_algorithm_num(for_emu=for_emu),
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
id: 1010
|
|
20
20
|
name: ivcurve
|
|
21
21
|
description: Postpone harvesting by sampling ivcurves (voltage stepped as sawtooth-wave)
|
|
22
|
-
comment: ~
|
|
22
|
+
comment: ~110 Hz, Between 50 & 60 Hz line-frequency to avoid standing waves
|
|
23
23
|
inherit_from: neutral
|
|
24
24
|
algorithm: ivcurve
|
|
25
|
-
samples_n:
|
|
25
|
+
samples_n: 909
|
|
26
26
|
voltage_min_mV: 0
|
|
27
27
|
voltage_max_mV: 5000
|
|
28
|
-
wait_cycles:
|
|
28
|
+
wait_cycles: 0
|
|
29
29
|
rising: false # downward sawtooth seems to have advantages for solar cells
|
|
30
30
|
# todo: also add switch for sawtooth- vs triangle-wave?
|
|
31
31
|
# todo: could also include a version with dynamic upper-boundary, varied if voc is reached very early
|
|
@@ -38,20 +38,17 @@
|
|
|
38
38
|
|
|
39
39
|
- datatype: VirtualHarvesterConfig
|
|
40
40
|
parameters:
|
|
41
|
-
id:
|
|
42
|
-
name:
|
|
43
|
-
comment: Name relates to curves per second
|
|
41
|
+
id: 1013
|
|
42
|
+
name: iv110 # synonym
|
|
44
43
|
inherit_from: ivcurve
|
|
45
|
-
samples_n: 100
|
|
46
|
-
wait_cycles: 0
|
|
47
44
|
|
|
48
45
|
- datatype: VirtualHarvesterConfig
|
|
49
46
|
parameters:
|
|
50
|
-
id:
|
|
51
|
-
name:
|
|
52
|
-
comment:
|
|
47
|
+
id: 1012
|
|
48
|
+
name: iv1000
|
|
49
|
+
comment: Name relates to curves per second
|
|
53
50
|
inherit_from: ivcurve
|
|
54
|
-
samples_n:
|
|
51
|
+
samples_n: 100
|
|
55
52
|
wait_cycles: 0
|
|
56
53
|
|
|
57
54
|
- datatype: VirtualHarvesterConfig
|
|
@@ -61,7 +58,7 @@
|
|
|
61
58
|
description: Postpone harvesting by sampling short circuit current & open circuit voltage
|
|
62
59
|
inherit_from: neutral
|
|
63
60
|
algorithm: isc_voc
|
|
64
|
-
wait_cycles:
|
|
61
|
+
wait_cycles: 4 # results in 10 kHz (isc, wait, voc, wait)
|
|
65
62
|
|
|
66
63
|
- datatype: VirtualHarvesterConfig
|
|
67
64
|
parameters:
|
|
@@ -76,6 +76,8 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
76
76
|
# final (always last) stage to compensate undetectable current spikes
|
|
77
77
|
# when enabling power for target
|
|
78
78
|
C_output_uF: Annotated[float, Field(ge=0, le=4.29e6)] = 1.0
|
|
79
|
+
# TODO: C_output is handled internally as delta-V, but should be a I_transient
|
|
80
|
+
# that makes it visible in simulation as additional i_out_drain
|
|
79
81
|
|
|
80
82
|
# Extra
|
|
81
83
|
V_output_log_gpio_threshold_mV: Annotated[float, Field(ge=0, le=4.29e6)] = 1_400
|
|
@@ -93,7 +95,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
93
95
|
# influence of cap-voltage is not implemented
|
|
94
96
|
LUT_input_V_min_log2_uV: Annotated[int, Field(ge=0, le=20)] = 0
|
|
95
97
|
# ⤷ 2^7 = 128 uV -> LUT[0][:] is for inputs < 128 uV
|
|
96
|
-
LUT_input_I_min_log2_nA: Annotated[int, Field(ge=
|
|
98
|
+
LUT_input_I_min_log2_nA: Annotated[int, Field(ge=1, le=20)] = 1
|
|
97
99
|
# ⤷ 2^8 = 256 nA -> LUT[:][0] is for inputs < 256 nA
|
|
98
100
|
|
|
99
101
|
# Buck Converter
|
|
@@ -103,7 +105,7 @@ class VirtualSourceConfig(ContentModel, title="Config for the virtual Source"):
|
|
|
103
105
|
|
|
104
106
|
LUT_output_efficiency: LUT1D = 12 * [1.00]
|
|
105
107
|
# ⤷ array[12] depending on output_current
|
|
106
|
-
LUT_output_I_min_log2_nA: Annotated[int, Field(ge=
|
|
108
|
+
LUT_output_I_min_log2_nA: Annotated[int, Field(ge=1, le=20)] = 1
|
|
107
109
|
# ⤷ 2^8 = 256 nA -> LUT[0] is for inputs < 256 nA, see notes on LUT_input for explanation
|
|
108
110
|
|
|
109
111
|
@model_validator(mode="before")
|
|
@@ -318,8 +320,8 @@ class ConverterPRUConfig(ShpModel):
|
|
|
318
320
|
V_buck_drop_uV=round(data.V_buck_drop_mV * 1e3),
|
|
319
321
|
# LUTs
|
|
320
322
|
LUT_input_V_min_log2_uV=data.LUT_input_V_min_log2_uV,
|
|
321
|
-
LUT_input_I_min_log2_nA=data.LUT_input_I_min_log2_nA,
|
|
322
|
-
LUT_output_I_min_log2_nA=data.LUT_output_I_min_log2_nA,
|
|
323
|
+
LUT_input_I_min_log2_nA=data.LUT_input_I_min_log2_nA - 1, # sub-1 due to later log2-op
|
|
324
|
+
LUT_output_I_min_log2_nA=data.LUT_output_I_min_log2_nA - 1, # sub-1 due to later log2
|
|
323
325
|
LUT_inp_efficiency_n8=[
|
|
324
326
|
[min(255, round(256 * ival)) for ival in il] for il in data.LUT_input_efficiency
|
|
325
327
|
],
|
|
@@ -56,16 +56,18 @@
|
|
|
56
56
|
[ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 ],
|
|
57
57
|
[ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 ],
|
|
58
58
|
[ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 ],
|
|
59
|
-
]
|
|
59
|
+
]
|
|
60
|
+
# input-array[12][12] depending on array[inp_voltage][log(inp_current)],
|
|
61
|
+
# influence of cap-voltage is not implemented
|
|
60
62
|
LUT_input_V_min_log2_uV: 0 # 2^7 = 128 uV -> array[0] is for inputs < 128 uV
|
|
61
|
-
LUT_input_I_min_log2_nA:
|
|
63
|
+
LUT_input_I_min_log2_nA: 1 # 2^8 = 256 nA -> array[0] is for inputs < 256 nA
|
|
62
64
|
|
|
63
65
|
# Buck-converter
|
|
64
66
|
V_output_mV: 2400
|
|
65
67
|
V_buck_drop_mV: 0.0 # simulate LDO min voltage differential or output-diode
|
|
66
68
|
|
|
67
69
|
LUT_output_efficiency: [ 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 ] # array[12] depending on output_current
|
|
68
|
-
LUT_output_I_min_log2_nA:
|
|
70
|
+
LUT_output_I_min_log2_nA: 1 # 2^8 = 256 nA -> array[0] is for inputs < 256 nA, see notes on LUT_input for explanation
|
|
69
71
|
|
|
70
72
|
owner: Ingmar
|
|
71
73
|
group: NES Lab
|
|
@@ -114,7 +116,7 @@
|
|
|
114
116
|
parameters:
|
|
115
117
|
id: 1020
|
|
116
118
|
name: BQ25504
|
|
117
|
-
description: TI BQ25504 with integrated boost-converter
|
|
119
|
+
description: TI BQ25504 with integrated boost-converter. Values are taken from the DK-Board.
|
|
118
120
|
inherit_from: neutral # to complete undefined vars
|
|
119
121
|
enable_boost: true # if false -> v_intermediate = v_input, output-switch-hysteresis is still usable
|
|
120
122
|
|
|
@@ -124,16 +126,16 @@
|
|
|
124
126
|
V_input_max_mV: 3000
|
|
125
127
|
I_input_max_mA: 100
|
|
126
128
|
|
|
127
|
-
C_intermediate_uF:
|
|
129
|
+
C_intermediate_uF: 100.0 # primary storage-Cap
|
|
128
130
|
V_intermediate_init_mV: 3000 # allow a proper / fast startup
|
|
129
131
|
I_intermediate_leak_nA: 330
|
|
130
132
|
|
|
131
|
-
V_intermediate_enable_threshold_mV:
|
|
132
|
-
V_intermediate_disable_threshold_mV:
|
|
133
|
+
V_intermediate_enable_threshold_mV: 1000 # -> target gets connected (hysteresis-combo with next value)
|
|
134
|
+
V_intermediate_disable_threshold_mV: 0 # -> target gets disconnected
|
|
133
135
|
interval_check_thresholds_ms: 64.0 # some BQs check every 64 ms if output should be disconnected
|
|
134
136
|
|
|
135
137
|
V_pwr_good_enable_threshold_mV: 2800 # target is informed by pwr-good on output-pin (hysteresis) -> for intermediate voltage
|
|
136
|
-
V_pwr_good_disable_threshold_mV:
|
|
138
|
+
V_pwr_good_disable_threshold_mV: 2340
|
|
137
139
|
immediate_pwr_good_signal: false # 1: activate instant schmitt-trigger, 0: stay in interval for checking thresholds
|
|
138
140
|
|
|
139
141
|
# Boost Converter
|
shepherd_core/reader.py
CHANGED
|
@@ -84,6 +84,7 @@ class Reader:
|
|
|
84
84
|
|
|
85
85
|
# init stats
|
|
86
86
|
self.runtime_s: float = 0
|
|
87
|
+
self.buffers_n: int = 0
|
|
87
88
|
self.file_size: int = 0
|
|
88
89
|
self.data_rate: float = 0
|
|
89
90
|
|
|
@@ -170,12 +171,16 @@ class Reader:
|
|
|
170
171
|
def _refresh_file_stats(self) -> None:
|
|
171
172
|
"""Update internal states, helpful after resampling or other changes in data-group."""
|
|
172
173
|
self.h5file.flush()
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
sample_count = self.ds_time.shape[0]
|
|
175
|
+
duration_raw = self.ds_time[sample_count - 1] - self.ds_time[0] if sample_count > 0 else 0
|
|
176
|
+
if (sample_count > 0) and (duration_raw > 0):
|
|
177
|
+
# this assumes iso-chronous sampling
|
|
178
|
+
duration_s = self._cal.time.raw_to_si(duration_raw)
|
|
179
|
+
self.sample_interval_s = duration_s / sample_count
|
|
176
180
|
self.sample_interval_ns = int(10**9 * self.sample_interval_s)
|
|
177
|
-
self.samplerate_sps = max(int(
|
|
181
|
+
self.samplerate_sps = max(int((sample_count - 1) / duration_s), 1)
|
|
178
182
|
self.runtime_s = round(self.ds_voltage.shape[0] / self.samplerate_sps, 1)
|
|
183
|
+
self.buffers_n = int(self.ds_voltage.shape[0] // self.samples_per_buffer)
|
|
179
184
|
if isinstance(self.file_path, Path):
|
|
180
185
|
self.file_size = self.file_path.stat().st_size
|
|
181
186
|
else:
|
shepherd_core/version.py
CHANGED
|
@@ -25,12 +25,28 @@ from ..data_models.content.virtual_source import ConverterPRUConfig
|
|
|
25
25
|
class PruCalibration:
|
|
26
26
|
"""part of calibration.h."""
|
|
27
27
|
|
|
28
|
+
# negative residue compensation - compensate for noise around 0
|
|
29
|
+
# -> current uint-design cuts away negative part and leads to biased mean()
|
|
30
|
+
NOISE_ESTIMATE_nA: int = 2000
|
|
31
|
+
RESIDUE_SIZE_FACTOR: int = 30
|
|
32
|
+
RESIDUE_MAX_nA: int = NOISE_ESTIMATE_nA * RESIDUE_SIZE_FACTOR
|
|
33
|
+
negative_residue_nA = 0
|
|
34
|
+
|
|
28
35
|
def __init__(self, cal_emu: Optional[CalibrationEmulator] = None) -> None:
|
|
29
36
|
self.cal = cal_emu if cal_emu else CalibrationEmulator()
|
|
30
37
|
|
|
31
38
|
def conv_adc_raw_to_nA(self, current_raw: int) -> float:
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
I_nA = self.cal.adc_C_A.raw_to_si(current_raw) * (10**9)
|
|
40
|
+
if self.cal.adc_C_A.offset < 0:
|
|
41
|
+
if I_nA > self.negative_residue_nA:
|
|
42
|
+
I_nA -= self.negative_residue_nA
|
|
43
|
+
self.negative_residue_nA = 0
|
|
44
|
+
else:
|
|
45
|
+
self.negative_residue_nA = self.negative_residue_nA - I_nA
|
|
46
|
+
if self.negative_residue_nA > self.RESIDUE_MAX_nA:
|
|
47
|
+
self.negative_residue_nA = self.RESIDUE_MAX_nA
|
|
48
|
+
I_nA = 0
|
|
49
|
+
return I_nA
|
|
34
50
|
|
|
35
51
|
@staticmethod
|
|
36
52
|
def conv_adc_raw_to_uV(voltage_raw: int) -> float:
|
|
@@ -88,10 +104,11 @@ class VirtualConverterModel:
|
|
|
88
104
|
self.vsource_skip_gpio_logging: bool = False
|
|
89
105
|
|
|
90
106
|
def calc_inp_power(self, input_voltage_uV: float, input_current_nA: float) -> int:
|
|
91
|
-
# Next 2 lines are Python-specific
|
|
107
|
+
# Next 2 lines are Python-specific (model unsigned int)
|
|
92
108
|
input_voltage_uV = max(0.0, input_voltage_uV)
|
|
93
109
|
input_current_nA = max(0.0, input_current_nA)
|
|
94
110
|
|
|
111
|
+
# Input diode
|
|
95
112
|
if input_voltage_uV > self._cfg.V_input_drop_uV:
|
|
96
113
|
input_voltage_uV -= self._cfg.V_input_drop_uV
|
|
97
114
|
else:
|
|
@@ -108,8 +125,6 @@ class VirtualConverterModel:
|
|
|
108
125
|
if self.enable_boost:
|
|
109
126
|
if input_voltage_uV < self._cfg.V_input_boost_threshold_uV:
|
|
110
127
|
input_voltage_uV = 0.0
|
|
111
|
-
if input_voltage_uV > self.V_mid_uV:
|
|
112
|
-
input_voltage_uV = self.V_mid_uV
|
|
113
128
|
elif not self.enable_storage:
|
|
114
129
|
# direct connection
|
|
115
130
|
self.V_mid_uV = input_voltage_uV
|
|
@@ -134,7 +149,7 @@ class VirtualConverterModel:
|
|
|
134
149
|
return round(self.P_inp_fW) # Python-specific, added for easier testing
|
|
135
150
|
|
|
136
151
|
def calc_out_power(self, current_adc_raw: int) -> int:
|
|
137
|
-
# Next 2 lines are Python-specific
|
|
152
|
+
# Next 2 lines are Python-specific (model unsigned int)
|
|
138
153
|
current_adc_raw = max(0, current_adc_raw)
|
|
139
154
|
current_adc_raw = min((2**18) - 1, current_adc_raw)
|
|
140
155
|
|
|
@@ -35,7 +35,11 @@ class VirtualHarvesterModel:
|
|
|
35
35
|
|
|
36
36
|
# INIT global vars: shared states
|
|
37
37
|
self.voltage_set_uV: int = self._cfg.voltage_uV + 1
|
|
38
|
-
self.
|
|
38
|
+
if self._cfg.interval_n > 2 * self._cfg.window_size:
|
|
39
|
+
self.interval_step = self._cfg.interval_n - (2 * self._cfg.window_size)
|
|
40
|
+
else:
|
|
41
|
+
self.interval_step = 2**30
|
|
42
|
+
# ⤷ intake two ivcurves before overflow / reset
|
|
39
43
|
self.is_rising: bool = (self._cfg.hrv_mode & (2**1)) != 0
|
|
40
44
|
|
|
41
45
|
# PO-Relevant, iv & adc
|
|
@@ -51,6 +55,7 @@ class VirtualHarvesterModel:
|
|
|
51
55
|
self.voltage_hold: int = 0
|
|
52
56
|
self.current_hold: int = 0
|
|
53
57
|
self.voltage_step_x4_uV: int = self._cfg.voltage_step_uV * 4
|
|
58
|
+
self.age_max: int = 2 * self._cfg.window_size
|
|
54
59
|
|
|
55
60
|
# INIT static vars: CV
|
|
56
61
|
self.voltage_last: int = 0
|
|
@@ -59,9 +64,10 @@ class VirtualHarvesterModel:
|
|
|
59
64
|
|
|
60
65
|
# INIT static vars: VOC
|
|
61
66
|
self.age_now: int = 0
|
|
62
|
-
self.voc_now: int =
|
|
67
|
+
self.voc_now: int = self._cfg.voltage_max_uV
|
|
63
68
|
self.age_nxt: int = 0
|
|
64
|
-
self.voc_nxt: int =
|
|
69
|
+
self.voc_nxt: int = self._cfg.voltage_max_uV
|
|
70
|
+
self.voc_min: int = max(1000, self._cfg.voltage_min_uV)
|
|
65
71
|
|
|
66
72
|
# INIT static vars: PO
|
|
67
73
|
# already done: interval step
|
|
@@ -110,20 +116,22 @@ class VirtualHarvesterModel:
|
|
|
110
116
|
return self.voltage_hold, self.current_hold
|
|
111
117
|
|
|
112
118
|
def ivcurve_2_mppt_voc(self, _voltage_uV: int, _current_nA: int) -> Tuple[int, int]:
|
|
113
|
-
self.interval_step =
|
|
119
|
+
self.interval_step = self.interval_step + 1
|
|
120
|
+
if self.interval_step >= self._cfg.interval_n:
|
|
121
|
+
self.interval_step = 0
|
|
114
122
|
self.age_nxt += 1
|
|
115
123
|
self.age_now += 1
|
|
116
124
|
|
|
117
125
|
if (
|
|
118
126
|
(_current_nA < self._cfg.current_limit_nA)
|
|
119
127
|
and (_voltage_uV < self.voc_nxt)
|
|
120
|
-
and (_voltage_uV >= self.
|
|
128
|
+
and (_voltage_uV >= self.voc_min)
|
|
121
129
|
and (_voltage_uV <= self._cfg.voltage_max_uV)
|
|
122
130
|
):
|
|
123
131
|
self.voc_nxt = _voltage_uV
|
|
124
132
|
self.age_nxt = 0
|
|
125
133
|
|
|
126
|
-
if (self.age_now > self.
|
|
134
|
+
if (self.age_now > self.age_max) or (self.voc_nxt <= self.voc_now):
|
|
127
135
|
self.age_now = self.age_nxt
|
|
128
136
|
self.voc_now = self.voc_nxt
|
|
129
137
|
self.age_nxt = 0
|
|
@@ -137,7 +145,9 @@ class VirtualHarvesterModel:
|
|
|
137
145
|
return _voltage_uV, _current_nA
|
|
138
146
|
|
|
139
147
|
def ivcurve_2_mppt_po(self, _voltage_uV: int, _current_nA: int) -> Tuple[int, int]:
|
|
140
|
-
self.interval_step =
|
|
148
|
+
self.interval_step = self.interval_step + 1
|
|
149
|
+
if self.interval_step >= self._cfg.interval_n:
|
|
150
|
+
self.interval_step = 0
|
|
141
151
|
|
|
142
152
|
_voltage_uV, _current_nA = self.ivcurve_2_cv(_voltage_uV, _current_nA)
|
|
143
153
|
|
|
@@ -193,7 +203,7 @@ class VirtualHarvesterModel:
|
|
|
193
203
|
self.voltage_nxt = _voltage_uV
|
|
194
204
|
self.current_nxt = _current_nA
|
|
195
205
|
|
|
196
|
-
if (self.age_now > self.
|
|
206
|
+
if (self.age_now > self.age_max) or (self.power_nxt >= self.power_now):
|
|
197
207
|
self.age_now = self.age_nxt
|
|
198
208
|
self.power_now = self.power_nxt
|
|
199
209
|
self.voltage_now = self.voltage_nxt
|
shepherd_core/writer.py
CHANGED
|
@@ -149,9 +149,18 @@ class Writer(Reader):
|
|
|
149
149
|
raise ValueError(msg)
|
|
150
150
|
|
|
151
151
|
if self._modify:
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
self
|
|
152
|
+
if mode:
|
|
153
|
+
self._mode = mode
|
|
154
|
+
if not hasattr(self, "_mode"):
|
|
155
|
+
self._mode = self.mode_default
|
|
156
|
+
if datatype:
|
|
157
|
+
self._datatype = datatype
|
|
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
|
|
155
164
|
else:
|
|
156
165
|
self._mode = mode if isinstance(mode, str) else self.mode_default
|
|
157
166
|
self._datatype = (
|
|
@@ -335,6 +344,7 @@ class Writer(Reader):
|
|
|
335
344
|
current: ndarray in physical-unit A
|
|
336
345
|
|
|
337
346
|
"""
|
|
347
|
+
# TODO: make timestamp optional to add it raw, OR unify append with granular raw-switch
|
|
338
348
|
timestamp = self._cal.time.si_to_raw(timestamp)
|
|
339
349
|
voltage = self._cal.voltage.si_to_raw(voltage)
|
|
340
350
|
current = self._cal.current.si_to_raw(current)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: shepherd_core
|
|
3
|
-
Version: 2024.
|
|
3
|
+
Version: 2024.8.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>
|
|
@@ -136,28 +136,30 @@ Notes:
|
|
|
136
136
|
The Library is available via PyPI and can be installed with
|
|
137
137
|
|
|
138
138
|
```shell
|
|
139
|
-
|
|
139
|
+
pip install shepherd-core -U
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
# or for the full experience (includes core)
|
|
142
|
+
pip install shepherd-data -U
|
|
143
143
|
```
|
|
144
144
|
|
|
145
145
|
For bleeding-edge-features or dev-work it is possible to install directly from GitHub-Sources (here `dev`-branch):
|
|
146
146
|
|
|
147
147
|
```Shell
|
|
148
148
|
pip install git+https://github.com/orgua/shepherd-datalib.git@dev#subdirectory=shepherd_core -U
|
|
149
|
+
# 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
|
|
149
151
|
```
|
|
150
152
|
|
|
151
153
|
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.
|
|
152
154
|
|
|
153
155
|
```shell
|
|
154
|
-
|
|
156
|
+
sudo apt install build-essential
|
|
155
157
|
```
|
|
156
158
|
|
|
157
159
|
For more advanced work with ``.elf``-files (modify value of symbols / target-ID) you should install
|
|
158
160
|
|
|
159
161
|
```shell
|
|
160
|
-
|
|
162
|
+
pip install shepherd-core[elf]
|
|
161
163
|
```
|
|
162
164
|
|
|
163
165
|
and also make sure the prereqs for the [pwntools](https://docs.pwntools.com/en/stable/install.html) are met.
|
|
@@ -165,7 +167,7 @@ and also make sure the prereqs for the [pwntools](https://docs.pwntools.com/en/s
|
|
|
165
167
|
For creating an inventory of the host-system you should install
|
|
166
168
|
|
|
167
169
|
```shell
|
|
168
|
-
|
|
170
|
+
pip install shepherd-core[inventory]
|
|
169
171
|
```
|
|
170
172
|
|
|
171
173
|
## Unittests
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
shepherd_core/__init__.py,sha256=QyqENyf508XfZQ4vDU5o6UL9rmIqkf8kzwgTF9XU1-Y,1270
|
|
2
|
-
shepherd_core/calibration_hw_def.py,sha256=
|
|
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=xpzwt1JjubNat0PNwdzmBJltLPOmvCyQGwcC-bq83ZI,27052
|
|
6
|
+
shepherd_core/version.py,sha256=xmbickJLTzW9cd7jhYX7fdLN72fW-vKC8QWUEFq3lR4,75
|
|
7
|
+
shepherd_core/writer.py,sha256=t53fXkYUWHhYnOFnHCoMWwUADkgYydy9Dg66PiqPa68,14946
|
|
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
|
|
11
11
|
shepherd_core/data_models/base/__init__.py,sha256=PSJ6acWViqBm0Eiom8DIgKfFVrp5lzYr8OsDvP79vwI,94
|
|
12
12
|
shepherd_core/data_models/base/cal_measurement.py,sha256=YScPG7QLynbUHdjcznYqU8O5KRh0XiROGxGSk9BETMk,3357
|
|
13
|
-
shepherd_core/data_models/base/calibration.py,sha256=
|
|
13
|
+
shepherd_core/data_models/base/calibration.py,sha256=k5VwAK_cwr0a6QY82Lw-9uHth5KUReFOakuOTzghoso,10725
|
|
14
14
|
shepherd_core/data_models/base/content.py,sha256=13j7GSgT73xn27jgDP508thUEJR4U-nCb5n7CJ50c9Y,2463
|
|
15
15
|
shepherd_core/data_models/base/shepherd.py,sha256=DNrx59o1VBuy_liJuUzZRzmTTYB73D_pUWiNyMQyjYY,6112
|
|
16
16
|
shepherd_core/data_models/base/timezone.py,sha256=2T6E46hJ1DAvmqKfu6uIgCK3RSoAKjGXRyzYNaqKyjY,665
|
|
@@ -21,10 +21,10 @@ shepherd_core/data_models/content/energy_environment.py,sha256=bXInmHzlRjBAt7mit
|
|
|
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=52HNac5k_GBCAhT2jBgu8oIqnvwMN3WMVmsAJrrxVRo,9678
|
|
25
|
+
shepherd_core/data_models/content/virtual_harvester_fixture.yaml,sha256=1u-V9RvFhbU74wEUv-BMQugh7OdzBE1SvhVe1v0DN-8,4251
|
|
26
|
+
shepherd_core/data_models/content/virtual_source.py,sha256=2ebNI1CvZgcfY5nz9P9p-Vr26E9l9bli9YeEd6_7yBY,14364
|
|
27
|
+
shepherd_core/data_models/content/virtual_source_fixture.yaml,sha256=qmtZp4Tcf5K8l65AjqOZhH-kDAaXs7YUIZNjJwaQhm8,10526
|
|
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
|
|
@@ -67,11 +67,11 @@ 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=dS33KYLq5GQ9_D8HfdP8iWSocWTghCi2ZZG2AJWNfaM,391
|
|
70
|
-
shepherd_core/vsource/virtual_converter_model.py,sha256=
|
|
71
|
-
shepherd_core/vsource/virtual_harvester_model.py,sha256=
|
|
70
|
+
shepherd_core/vsource/virtual_converter_model.py,sha256=sQkJSj-7CVabHvXqk6C3cbLmztSSsdrSU3WgYr4h30E,11067
|
|
71
|
+
shepherd_core/vsource/virtual_harvester_model.py,sha256=ROR8vtKeM2WTnogV68TKBOu0zRVwOwQj_q67hW_qtpQ,8297
|
|
72
72
|
shepherd_core/vsource/virtual_source_model.py,sha256=fjN8myTY3I_LpikF_aGAcxes3RGu1GP23P7XKC_UIyA,2737
|
|
73
|
-
shepherd_core-2024.
|
|
74
|
-
shepherd_core-2024.
|
|
75
|
-
shepherd_core-2024.
|
|
76
|
-
shepherd_core-2024.
|
|
77
|
-
shepherd_core-2024.
|
|
73
|
+
shepherd_core-2024.8.1.dist-info/METADATA,sha256=-DJfS_CoIctcSz8u4V-85AUpTYrcLVDZ0Pc8UQScNJI,7771
|
|
74
|
+
shepherd_core-2024.8.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
75
|
+
shepherd_core-2024.8.1.dist-info/top_level.txt,sha256=wy-t7HRBrKARZxa-Y8_j8d49oVHnulh-95K9ikxVhew,14
|
|
76
|
+
shepherd_core-2024.8.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
77
|
+
shepherd_core-2024.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|