shepherd-data 2023.2.1__py3-none-any.whl → 2023.3.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_data/__init__.py +2 -1
- shepherd_data/calibration.py +17 -9
- shepherd_data/cli.py +24 -12
- shepherd_data/ivonne.py +35 -24
- shepherd_data/mppt.py +12 -7
- shepherd_data/reader.py +118 -77
- shepherd_data/writer.py +111 -95
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/METADATA +40 -30
- shepherd_data-2023.3.1.dist-info/RECORD +14 -0
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/WHEEL +1 -1
- shepherd_data-2023.2.1.dist-info/RECORD +0 -14
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/LICENSE +0 -0
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/entry_points.txt +0 -0
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/top_level.txt +0 -0
- {shepherd_data-2023.2.1.dist-info → shepherd_data-2023.3.1.dist-info}/zip-safe +0 -0
shepherd_data/__init__.py
CHANGED
shepherd_data/calibration.py
CHANGED
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
4
|
from typing import Dict
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import TypeVar
|
|
6
6
|
|
|
7
7
|
import numpy as np
|
|
8
|
+
from numpy.typing import NDArray
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
cal_default: Dict[str, dict] = {
|
|
10
|
+
cal_default: Dict[str, Dict[str, float]] = {
|
|
11
11
|
"voltage": {"gain": 3 * 1e-9, "offset": 0.0}, # allows 0 - 12 V in 3 nV-Steps
|
|
12
12
|
"current": {"gain": 250 * 1e-12, "offset": 0.0}, # allows 0 - 1 A in 250 pA - Steps
|
|
13
13
|
"time": {"gain": 1e-9, "offset": 0.0},
|
|
14
|
+
# SI-value [SI-Unit] = raw-value * gain + offset
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
T_calc = TypeVar("T_calc", NDArray[np.float64], float)
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
) -> Union[np.ndarray, float]:
|
|
19
|
+
|
|
20
|
+
def raw_to_si(values_raw: T_calc, cal: Dict[str, float]) -> T_calc:
|
|
20
21
|
"""Helper to convert between physical units and raw unsigned integers
|
|
21
22
|
|
|
22
23
|
:param values_raw: number or numpy array with raw values
|
|
@@ -24,11 +25,15 @@ def raw_to_si(
|
|
|
24
25
|
:return: converted number or array
|
|
25
26
|
"""
|
|
26
27
|
values_si = values_raw * cal["gain"] + cal["offset"]
|
|
27
|
-
values_si
|
|
28
|
+
if isinstance(values_si, np.ndarray):
|
|
29
|
+
values_si[values_si < 0.0] = 0.0
|
|
30
|
+
# if pyright still complains, cast with .astype(float)
|
|
31
|
+
else:
|
|
32
|
+
values_si = float(max(values_si, 0.0))
|
|
28
33
|
return values_si
|
|
29
34
|
|
|
30
35
|
|
|
31
|
-
def si_to_raw(values_si:
|
|
36
|
+
def si_to_raw(values_si: T_calc, cal: Dict[str, float]) -> T_calc:
|
|
32
37
|
"""Helper to convert between physical units and raw unsigned integers
|
|
33
38
|
|
|
34
39
|
:param values_si: number or numpy array with values in physical units
|
|
@@ -36,5 +41,8 @@ def si_to_raw(values_si: Union[np.ndarray, float], cal: dict) -> Union[np.ndarra
|
|
|
36
41
|
:return: converted number or array
|
|
37
42
|
"""
|
|
38
43
|
values_raw = (values_si - cal["offset"]) / cal["gain"]
|
|
39
|
-
values_raw
|
|
44
|
+
if isinstance(values_raw, np.ndarray):
|
|
45
|
+
values_raw[values_raw < 0.0] = 0.0
|
|
46
|
+
else:
|
|
47
|
+
values_raw = max(values_raw, 0.0)
|
|
40
48
|
return values_raw
|
shepherd_data/cli.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import sys
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import List
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import Optional
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ logger = logging.getLogger("SHPData.cli")
|
|
|
18
18
|
verbose_level = 2
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def config_logger(verbose: int) ->
|
|
21
|
+
def config_logger(verbose: int) -> None:
|
|
22
22
|
# TODO: put in __init__, provide logger, ditch global var
|
|
23
23
|
if verbose == 0:
|
|
24
24
|
logger.setLevel(logging.ERROR)
|
|
@@ -61,8 +61,8 @@ def path_to_flist(data_path: Path) -> List[Path]:
|
|
|
61
61
|
default=0,
|
|
62
62
|
help="4 Levels [0..3](Error, Warning, Info, Debug)",
|
|
63
63
|
)
|
|
64
|
-
@click.pass_context
|
|
65
|
-
def cli(ctx, verbose: int):
|
|
64
|
+
@click.pass_context # TODO: is the ctx-type correct?
|
|
65
|
+
def cli(ctx: click.Context, verbose: int) -> None:
|
|
66
66
|
"""Shepherd: Synchronized Energy Harvesting Emulator and Recorder
|
|
67
67
|
|
|
68
68
|
Args:
|
|
@@ -79,7 +79,7 @@ def cli(ctx, verbose: int):
|
|
|
79
79
|
|
|
80
80
|
@cli.command(short_help="Validates a file or directory containing shepherd-recordings")
|
|
81
81
|
@click.argument("in_data", type=click.Path(exists=True, resolve_path=True))
|
|
82
|
-
def validate(in_data):
|
|
82
|
+
def validate(in_data: Path) -> None:
|
|
83
83
|
"""Validates a file or directory containing shepherd-recordings"""
|
|
84
84
|
files = path_to_flist(in_data)
|
|
85
85
|
valid_dir = True
|
|
@@ -111,7 +111,7 @@ def validate(in_data):
|
|
|
111
111
|
type=click.STRING,
|
|
112
112
|
help="Set an individual csv-separator",
|
|
113
113
|
)
|
|
114
|
-
def extract(in_data, ds_factor, separator):
|
|
114
|
+
def extract(in_data: Path, ds_factor: float, separator: str) -> None:
|
|
115
115
|
"""Extracts recorded IVSamples and stores it to csv"""
|
|
116
116
|
files = path_to_flist(in_data)
|
|
117
117
|
if not isinstance(ds_factor, (float, int)) or ds_factor < 1:
|
|
@@ -160,7 +160,7 @@ def extract(in_data, ds_factor, separator):
|
|
|
160
160
|
type=click.STRING,
|
|
161
161
|
help="Set an individual csv-separator",
|
|
162
162
|
)
|
|
163
|
-
def extract_meta(in_data, separator):
|
|
163
|
+
def extract_meta(in_data: Path, separator: str) -> None:
|
|
164
164
|
"""Extracts metadata and logs from file or directory containing shepherd-recordings"""
|
|
165
165
|
files = path_to_flist(in_data)
|
|
166
166
|
for file in files:
|
|
@@ -197,14 +197,16 @@ def extract_meta(in_data, separator):
|
|
|
197
197
|
@click.option(
|
|
198
198
|
"--sample-rate",
|
|
199
199
|
"-r",
|
|
200
|
-
type=
|
|
200
|
+
type=click.INT,
|
|
201
201
|
help="Alternative Input to determine a downsample-factor (Choose One)",
|
|
202
202
|
)
|
|
203
|
-
def downsample(
|
|
203
|
+
def downsample(
|
|
204
|
+
in_data: Path, ds_factor: Optional[float], sample_rate: Optional[int]
|
|
205
|
+
) -> None:
|
|
204
206
|
"""Creates an array of downsampling-files from file
|
|
205
207
|
or directory containing shepherd-recordings"""
|
|
206
208
|
if ds_factor is None and sample_rate is not None and sample_rate >= 1:
|
|
207
|
-
ds_factor = int(Reader.
|
|
209
|
+
ds_factor = int(Reader.samplerate_sps_default / sample_rate)
|
|
208
210
|
if isinstance(ds_factor, (float, int)) and ds_factor >= 1:
|
|
209
211
|
ds_list = [ds_factor]
|
|
210
212
|
else:
|
|
@@ -279,7 +281,14 @@ def downsample(in_data, ds_factor, sample_rate):
|
|
|
279
281
|
is_flag=True,
|
|
280
282
|
help="Plot all files (in directory) into one Multiplot",
|
|
281
283
|
)
|
|
282
|
-
def plot(
|
|
284
|
+
def plot(
|
|
285
|
+
in_data: Path,
|
|
286
|
+
start: Optional[float],
|
|
287
|
+
end: Optional[float],
|
|
288
|
+
width: int,
|
|
289
|
+
height: int,
|
|
290
|
+
multiplot: bool,
|
|
291
|
+
) -> None:
|
|
283
292
|
"""Plots IV-trace from file or directory containing shepherd-recordings"""
|
|
284
293
|
files = path_to_flist(in_data)
|
|
285
294
|
multiplot = multiplot and len(files) > 1
|
|
@@ -294,7 +303,10 @@ def plot(in_data, start: float, end: float, width: int, height: int, multiplot:
|
|
|
294
303
|
if multiplot:
|
|
295
304
|
logger.info("Got %d datasets to plot", len(data))
|
|
296
305
|
mpl_path = Reader.multiplot_to_file(data, in_data, width, height)
|
|
297
|
-
|
|
306
|
+
if mpl_path:
|
|
307
|
+
logger.info("Plot generated and saved to '%s'", mpl_path.name)
|
|
308
|
+
else:
|
|
309
|
+
logger.info("Plot not generated, path was already in use.")
|
|
298
310
|
|
|
299
311
|
|
|
300
312
|
if __name__ == "__main__":
|
shepherd_data/ivonne.py
CHANGED
|
@@ -9,7 +9,7 @@ import math
|
|
|
9
9
|
import os
|
|
10
10
|
import pickle # noqa: S403
|
|
11
11
|
from pathlib import Path
|
|
12
|
-
from typing import
|
|
12
|
+
from typing import Optional
|
|
13
13
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
import pandas as pd
|
|
@@ -34,27 +34,30 @@ def get_isc(coeffs: pd.DataFrame):
|
|
|
34
34
|
class Reader:
|
|
35
35
|
"""container for converters to shepherd-data"""
|
|
36
36
|
|
|
37
|
-
samplerate_sps: int = 50
|
|
38
|
-
sample_interval_ns: int = int(10**9 // samplerate_sps)
|
|
39
|
-
sample_interval_s: float = 1 / samplerate_sps
|
|
40
|
-
|
|
41
|
-
runtime_s: float = None
|
|
42
|
-
file_size: int = None
|
|
43
|
-
data_rate: float = None
|
|
44
|
-
|
|
45
|
-
_df: pd.DataFrame = None
|
|
46
|
-
|
|
47
37
|
_logger: logging.Logger = logging.getLogger("SHPData.IVonne.Reader")
|
|
48
38
|
|
|
49
39
|
def __init__(
|
|
50
|
-
self,
|
|
40
|
+
self,
|
|
41
|
+
file_path: Path,
|
|
42
|
+
samplerate_sps: Optional[int] = None,
|
|
43
|
+
verbose: bool = True,
|
|
51
44
|
):
|
|
52
45
|
self._logger.setLevel(logging.INFO if verbose else logging.WARNING)
|
|
53
46
|
|
|
54
47
|
self.file_path = Path(file_path)
|
|
48
|
+
self.samplerate_sps: int = 50
|
|
55
49
|
if samplerate_sps is not None:
|
|
56
50
|
self.samplerate_sps = samplerate_sps
|
|
57
51
|
|
|
52
|
+
self.sample_interval_ns: int = int(10**9 // self.samplerate_sps)
|
|
53
|
+
self.sample_interval_s: float = 1 / self.samplerate_sps
|
|
54
|
+
|
|
55
|
+
self.runtime_s: float = 0
|
|
56
|
+
self.file_size: int = 0
|
|
57
|
+
self.data_rate: float = 0
|
|
58
|
+
|
|
59
|
+
self._df: Optional[pd.DataFrame] = None
|
|
60
|
+
|
|
58
61
|
def __enter__(self):
|
|
59
62
|
if not self.file_path.exists():
|
|
60
63
|
raise FileNotFoundError(
|
|
@@ -75,10 +78,12 @@ class Reader:
|
|
|
75
78
|
)
|
|
76
79
|
return self
|
|
77
80
|
|
|
78
|
-
def __exit__(self, *exc):
|
|
81
|
+
def __exit__(self, *exc): # type: ignore
|
|
79
82
|
pass
|
|
80
83
|
|
|
81
|
-
def _refresh_file_stats(self) ->
|
|
84
|
+
def _refresh_file_stats(self) -> None:
|
|
85
|
+
if self._df is None:
|
|
86
|
+
raise RuntimeError("IVonne Context was not entered - file not open!")
|
|
82
87
|
self.runtime_s = round(self._df.shape[0] / self.samplerate_sps, 3)
|
|
83
88
|
self.file_size = self.file_path.stat().st_size
|
|
84
89
|
self.data_rate = self.file_size / self.runtime_s if self.runtime_s > 0 else 0
|
|
@@ -88,8 +93,8 @@ class Reader:
|
|
|
88
93
|
shp_output: Path,
|
|
89
94
|
v_max: float = 5.0,
|
|
90
95
|
pts_per_curve: int = 1000,
|
|
91
|
-
duration_s: float = None,
|
|
92
|
-
) ->
|
|
96
|
+
duration_s: Optional[float] = None,
|
|
97
|
+
) -> None:
|
|
93
98
|
"""Transforms previously recorded parameters to shepherd hdf database with IV curves.
|
|
94
99
|
Shepherd should work with IV 'surfaces', where we have a stream of IV curves
|
|
95
100
|
|
|
@@ -98,6 +103,8 @@ class Reader:
|
|
|
98
103
|
:param pts_per_curve: Number of sampling points of the prototype curve
|
|
99
104
|
:param duration_s: time to stop in seconds, counted from beginning
|
|
100
105
|
"""
|
|
106
|
+
if self._df is None:
|
|
107
|
+
raise RuntimeError("IVonne Context was not entered - file not open!")
|
|
101
108
|
if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
|
|
102
109
|
self._logger.info(" -> gets trimmed to %s s", duration_s)
|
|
103
110
|
df_elements_n = min(
|
|
@@ -124,7 +131,7 @@ class Reader:
|
|
|
124
131
|
for idx in job_iter:
|
|
125
132
|
idx_top = min(idx + max_elements, df_elements_n)
|
|
126
133
|
df_slice = self._df.iloc[idx : idx_top + 1].copy()
|
|
127
|
-
df_slice
|
|
134
|
+
df_slice["timestamp"] = pd.TimedeltaIndex(
|
|
128
135
|
data=df_slice["time"], unit="s"
|
|
129
136
|
)
|
|
130
137
|
df_slice = df_slice.set_index("timestamp")
|
|
@@ -152,9 +159,9 @@ class Reader:
|
|
|
152
159
|
self,
|
|
153
160
|
shp_output: Path,
|
|
154
161
|
v_max: float = 5.0,
|
|
155
|
-
duration_s: float = None,
|
|
156
|
-
tracker: MPPTracker = None,
|
|
157
|
-
) ->
|
|
162
|
+
duration_s: Optional[float] = None,
|
|
163
|
+
tracker: Optional[MPPTracker] = None,
|
|
164
|
+
) -> None:
|
|
158
165
|
"""Transforms shepherd IV curves to shepherd IV traces.
|
|
159
166
|
|
|
160
167
|
For the 'buck' and 'buck-boost' modes, shepherd takes voltage and current traces.
|
|
@@ -172,6 +179,8 @@ class Reader:
|
|
|
172
179
|
:param duration_s: time to stop in seconds, counted from beginning
|
|
173
180
|
:param tracker: VOC or OPT
|
|
174
181
|
"""
|
|
182
|
+
if self._df is None:
|
|
183
|
+
raise RuntimeError("IVonne Context was not entered - file not open!")
|
|
175
184
|
if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
|
|
176
185
|
self._logger.info(" -> gets trimmed to %s s", duration_s)
|
|
177
186
|
df_elements_n = min(
|
|
@@ -204,7 +213,7 @@ class Reader:
|
|
|
204
213
|
df_slice.loc[:, "voc"] = get_voc(df_slice)
|
|
205
214
|
df_slice.loc[df_slice["voc"] >= v_max, "voc"] = v_max
|
|
206
215
|
df_slice = tracker.process(df_slice)
|
|
207
|
-
df_slice
|
|
216
|
+
df_slice["timestamp"] = pd.TimedeltaIndex(
|
|
208
217
|
data=df_slice["time"], unit="s"
|
|
209
218
|
)
|
|
210
219
|
df_slice = df_slice[["time", "v", "i", "timestamp"]].set_index(
|
|
@@ -226,14 +235,16 @@ class Reader:
|
|
|
226
235
|
self,
|
|
227
236
|
shp_output: Path,
|
|
228
237
|
v_max: float = 5.0,
|
|
229
|
-
duration_s: float = None,
|
|
230
|
-
) ->
|
|
238
|
+
duration_s: Optional[float] = None,
|
|
239
|
+
) -> None:
|
|
231
240
|
"""Transforms ivonne-parameters to upsampled version for shepherd
|
|
232
241
|
|
|
233
242
|
:param shp_output: Path where the resulting hdf file shall be stored
|
|
234
243
|
:param v_max: Maximum voltage supported by shepherd
|
|
235
244
|
:param duration_s: time to stop in seconds, counted from beginning
|
|
236
245
|
"""
|
|
246
|
+
if self._df is None:
|
|
247
|
+
raise RuntimeError("IVonne Context was not entered - file not open!")
|
|
237
248
|
if isinstance(duration_s, (float, int)) and self.runtime_s > duration_s:
|
|
238
249
|
self._logger.info(" -> gets trimmed to %s s", duration_s)
|
|
239
250
|
df_elements_n = min(
|
|
@@ -261,7 +272,7 @@ class Reader:
|
|
|
261
272
|
df_slice.loc[:, "voc"] = get_voc(df_slice)
|
|
262
273
|
df_slice.loc[df_slice["voc"] >= v_max, "voc"] = v_max
|
|
263
274
|
df_slice.loc[:, "isc"] = get_isc(df_slice)
|
|
264
|
-
df_slice
|
|
275
|
+
df_slice["timestamp"] = pd.TimedeltaIndex(
|
|
265
276
|
data=df_slice["time"], unit="s"
|
|
266
277
|
)
|
|
267
278
|
df_slice = df_slice[["time", "voc", "isc", "timestamp"]].set_index(
|
shepherd_data/mppt.py
CHANGED
|
@@ -5,8 +5,10 @@ Might be exchanged by shepherds py-model of pru-harvesters
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
|
+
from .calibration import T_calc
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
def iv_model(voltages: T_calc, coeffs: pd.Series) -> T_calc:
|
|
10
12
|
"""Simple diode based model of a solar panel IV curve.
|
|
11
13
|
|
|
12
14
|
Args:
|
|
@@ -16,15 +18,18 @@ def iv_model(voltages: np.ndarray, coeffs: pd.DataFrame) -> np.ndarray:
|
|
|
16
18
|
Returns:
|
|
17
19
|
Solar current at given load voltage
|
|
18
20
|
"""
|
|
19
|
-
currents = coeffs["a"] - coeffs["b"] * (
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
currents = float(coeffs["a"]) - float(coeffs["b"]) * (
|
|
22
|
+
np.exp(float(coeffs["c"]) * voltages) - 1.0
|
|
23
|
+
)
|
|
24
|
+
if isinstance(currents, np.ndarray):
|
|
25
|
+
currents[currents < 0.0] = 0.0
|
|
22
26
|
else:
|
|
23
|
-
currents = max(0, currents)
|
|
27
|
+
currents = max(0.0, currents)
|
|
28
|
+
|
|
24
29
|
return currents
|
|
25
30
|
|
|
26
31
|
|
|
27
|
-
def find_oc(v_arr, i_arr, ratio: float = 0.05):
|
|
32
|
+
def find_oc(v_arr: np.ndarray, i_arr: np.ndarray, ratio: float = 0.05):
|
|
28
33
|
"""Approximates opencircuit voltage.
|
|
29
34
|
|
|
30
35
|
Searches last current value that is above a certain ratio of the short-circuit
|
|
@@ -51,7 +56,7 @@ class MPPTracker:
|
|
|
51
56
|
:param coeffs: ivonne coefficients
|
|
52
57
|
:return:
|
|
53
58
|
"""
|
|
54
|
-
|
|
59
|
+
return pd.DataFrame()
|
|
55
60
|
|
|
56
61
|
|
|
57
62
|
class OpenCircuitTracker(MPPTracker):
|