pqopen-lib 0.7.9__py3-none-any.whl → 0.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.
- pqopen/powersystem.py +93 -16
- pqopen/zcd.py +17 -8
- {pqopen_lib-0.7.9.dist-info → pqopen_lib-0.8.1.dist-info}/METADATA +1 -1
- {pqopen_lib-0.7.9.dist-info → pqopen_lib-0.8.1.dist-info}/RECORD +6 -6
- {pqopen_lib-0.7.9.dist-info → pqopen_lib-0.8.1.dist-info}/WHEEL +0 -0
- {pqopen_lib-0.7.9.dist-info → pqopen_lib-0.8.1.dist-info}/licenses/LICENSE +0 -0
pqopen/powersystem.py
CHANGED
|
@@ -89,7 +89,8 @@ class PowerSystem(object):
|
|
|
89
89
|
"debug_channels": False,
|
|
90
90
|
"energy_channels": {},
|
|
91
91
|
"one_period_fundamental": 0,
|
|
92
|
-
"rms_trapz_rule": False
|
|
92
|
+
"rms_trapz_rule": False,
|
|
93
|
+
"pmu_calculation": False}
|
|
93
94
|
self._prepare_calc_channels()
|
|
94
95
|
self.output_channels: Dict[str, DataChannelBuffer] = {}
|
|
95
96
|
self._last_processed_sidx = 0
|
|
@@ -109,7 +110,8 @@ class PowerSystem(object):
|
|
|
109
110
|
self._calc_channels = {"half_period": {"voltage": {}, "current": {}, "power": {}, "_debug": {}},
|
|
110
111
|
"one_period": {"voltage": {}, "current": {}, "power": {}, "_debug": {}},
|
|
111
112
|
"one_period_ovlp": {"voltage": {}, "current": {}, "power": {}, "_debug": {}},
|
|
112
|
-
"multi_period": {"voltage": {}, "current": {}, "power": {}, "energy": {}, "_debug": {}}
|
|
113
|
+
"multi_period": {"voltage": {}, "current": {}, "power": {}, "energy": {}, "_debug": {}},
|
|
114
|
+
"pmu": {"voltage": {}, "current": {}, "power": {}, "_debug": {}}}
|
|
113
115
|
|
|
114
116
|
def add_phase(self, u_channel: AcqBuffer, i_channel: AcqBuffer = None, name: str = ""):
|
|
115
117
|
"""
|
|
@@ -230,6 +232,19 @@ class PowerSystem(object):
|
|
|
230
232
|
"""
|
|
231
233
|
self._features["rms_trapz_rule"] = True
|
|
232
234
|
|
|
235
|
+
def enable_pmu_calculation(self):
|
|
236
|
+
"""
|
|
237
|
+
Enables the calculation of equidistant PMU values
|
|
238
|
+
"""
|
|
239
|
+
if not self._features["nper_abs_time_sync"]:
|
|
240
|
+
logger.warning("To enable pmu_calculation, nper_abs_time_sync must be enabled first.")
|
|
241
|
+
return False
|
|
242
|
+
self._features["pmu_calculation"] = True
|
|
243
|
+
self._channel_update_needed = True
|
|
244
|
+
self._pmu_last_processed_sidx = 0
|
|
245
|
+
self._pmu_last_processed_ts_us = 0
|
|
246
|
+
self._pmu_time_increment_us = int(1_000_000 / self.nominal_frequency)
|
|
247
|
+
|
|
233
248
|
def _resync_nper_abs_time(self, zc_idx: int):
|
|
234
249
|
if not self._features["nper_abs_time_sync"]:
|
|
235
250
|
return None
|
|
@@ -278,6 +293,8 @@ class PowerSystem(object):
|
|
|
278
293
|
self._calc_channels["multi_period"]["energy"]["w_pos"].last_sample_value = self._features["energy_channels"]["energy_counters"].get("W_pos", 0.0)
|
|
279
294
|
self._calc_channels["multi_period"]["energy"]["w_neg"] = DataChannelBuffer('W_neg', agg_type='max', unit="Wh", dtype=np.float64)
|
|
280
295
|
self._calc_channels["multi_period"]["energy"]["w_neg"].last_sample_value = self._features["energy_channels"]["energy_counters"].get("W_neg", 0.0)
|
|
296
|
+
if self._features["pmu_calculation"]:
|
|
297
|
+
self._calc_channels["pmu"]["power"]["freq"] = DataChannelBuffer('Freq_pmu', agg_type='mean', unit="Hz")
|
|
281
298
|
|
|
282
299
|
for agg_interval, phys_types in self._calc_channels.items():
|
|
283
300
|
for phys_type, calc_type in phys_types.items():
|
|
@@ -340,6 +357,9 @@ class PowerSystem(object):
|
|
|
340
357
|
self._process_multi_period(self._zero_crossings[-self.nper - 1], self._zero_crossings[-1])
|
|
341
358
|
self._resync_nper_abs_time(-1)
|
|
342
359
|
self._process_fluctuation_calc(self._zero_crossings[-self.nper - 1], self._zero_crossings[-1])
|
|
360
|
+
|
|
361
|
+
# Process fixed-time (PMU) channels
|
|
362
|
+
self._process_pmu_calc(self._zero_crossings[-1])
|
|
343
363
|
|
|
344
364
|
self._last_processed_sidx = stop_acq_sidx
|
|
345
365
|
|
|
@@ -373,6 +393,13 @@ class PowerSystem(object):
|
|
|
373
393
|
u_values = phase._u_channel.read_data_by_index(phase_period_start_sidx, phase_period_stop_sidx)
|
|
374
394
|
if self._features["mains_signaling_tracer"]:
|
|
375
395
|
msv_edge, msv_value = phase._mains_signaling_tracer.process(u_values[::10])
|
|
396
|
+
if self._features["one_period_fundamental"]:
|
|
397
|
+
# Use sample-discrete frequency, not the exact one for full cycle
|
|
398
|
+
u_values_sync = phase._u_channel.read_data_by_index(period_start_sidx, period_stop_sidx)
|
|
399
|
+
self._fund_freq_list = np.roll(self._fund_freq_list,-1)
|
|
400
|
+
self._fund_freq_list[-1] = self._samplerate/len(u_values_sync)
|
|
401
|
+
mean_freq = self._fund_freq_list[self._fund_freq_list>0].mean()
|
|
402
|
+
fund_amp, fund_phase = calc_single_freq(u_values_sync, mean_freq, self._samplerate)
|
|
376
403
|
for phys_type, output_channel in phase._calc_channels["one_period"]["voltage"].items():
|
|
377
404
|
if phys_type == "trms":
|
|
378
405
|
if self._features["rms_trapz_rule"]:
|
|
@@ -399,12 +426,10 @@ class PowerSystem(object):
|
|
|
399
426
|
if phys_type == "slope":
|
|
400
427
|
output_channel.put_data_single(phase_period_stop_sidx, np.abs(np.diff(u_values)).max())
|
|
401
428
|
if phys_type == "fund_rms":
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
fund_amp, fund_phase = calc_single_freq(u_values, mean_freq, self._samplerate)
|
|
407
|
-
output_channel.put_data_single(phase_period_stop_sidx, fund_amp)
|
|
429
|
+
output_channel.put_data_single(period_stop_sidx, fund_amp)
|
|
430
|
+
if phys_type == "fund_phi":
|
|
431
|
+
output_channel.put_data_single(period_stop_sidx, pq.normalize_phi(np.rad2deg(fund_phase+np.pi/2)))
|
|
432
|
+
|
|
408
433
|
for phys_type, output_channel in phase._calc_channels["half_period"]["voltage"].items():
|
|
409
434
|
if phys_type == "trms":
|
|
410
435
|
# First half period
|
|
@@ -571,6 +596,52 @@ class PowerSystem(object):
|
|
|
571
596
|
self._pst_last_calc_sidx = stop_sidx
|
|
572
597
|
self._pst_next_round_ts = floor_timestamp(stop_ts, self._pst_interval_sec, ts_resolution="us")+self._pst_interval_sec*1_000_000
|
|
573
598
|
|
|
599
|
+
def _process_pmu_calc(self, stop_sidx: int):
|
|
600
|
+
"""
|
|
601
|
+
Process data to calculate PMU (Phasor Measurement Unit) parameters.
|
|
602
|
+
|
|
603
|
+
Parameters:
|
|
604
|
+
stop_sidx: Stop sample index for calculation
|
|
605
|
+
|
|
606
|
+
Returns:
|
|
607
|
+
None
|
|
608
|
+
"""
|
|
609
|
+
if not self._features["pmu_calculation"]:
|
|
610
|
+
return None
|
|
611
|
+
# Read timestamps
|
|
612
|
+
start_sidx = int(self._pmu_last_processed_sidx - 1.5*self._samplerate / self.nominal_frequency)
|
|
613
|
+
start_sidx = max(0, start_sidx)
|
|
614
|
+
ts_us = self._time_channel.read_data_by_index(start_sidx, stop_sidx)
|
|
615
|
+
first_pmu_ts = (self._pmu_last_processed_ts_us + self._pmu_time_increment_us) if self._pmu_last_processed_ts_us > 0 else ts_us[0] - ts_us[0] % self._pmu_time_increment_us + self._pmu_time_increment_us
|
|
616
|
+
last_pmu_ts = ts_us[-1] - (ts_us[-1] % self._pmu_time_increment_us) - self._pmu_time_increment_us # convert until n-1
|
|
617
|
+
logger.debug(f"first_pmu_ts: {first_pmu_ts:d}, last_pmu_ts: {last_pmu_ts:d}, self._pmu_time_increment_us: {self._pmu_time_increment_us:d}")
|
|
618
|
+
wanted_pmu_ts = np.arange(first_pmu_ts, last_pmu_ts+1, self._pmu_time_increment_us, dtype=np.int64)
|
|
619
|
+
wanted_pmu_sidx = [int(np.searchsorted(ts_us, pmu_ts)+start_sidx) for pmu_ts in wanted_pmu_ts]
|
|
620
|
+
wanted_pmu_ts_map = dict(zip(wanted_pmu_sidx, wanted_pmu_ts))
|
|
621
|
+
real_pmu_ts_map = {pmu_sidx: int(ts_us[pmu_sidx - start_sidx]) for pmu_sidx in wanted_pmu_sidx}
|
|
622
|
+
ovlp_window_start = max(0, int(wanted_pmu_sidx[0] - 1.5*self._samplerate / self.nominal_frequency))
|
|
623
|
+
freq, sample_indices = self._calc_channels["one_period"]["power"]["freq"].read_data_by_acq_sidx(ovlp_window_start, stop_sidx)
|
|
624
|
+
|
|
625
|
+
if len(sample_indices) == 0:
|
|
626
|
+
return None
|
|
627
|
+
for phase in self._phases:
|
|
628
|
+
u_raw = phase._u_channel.read_data_by_index(ovlp_window_start, stop_sidx)
|
|
629
|
+
for pmu_sidx in wanted_pmu_sidx:
|
|
630
|
+
# Search for previous zc
|
|
631
|
+
zc_idx = np.searchsorted(sample_indices, pmu_sidx,side="left")
|
|
632
|
+
if zc_idx == len(sample_indices):
|
|
633
|
+
zc_idx -= 1
|
|
634
|
+
local_stop_idx = max(0, pmu_sidx - ovlp_window_start)
|
|
635
|
+
local_start_idx = max(0, local_stop_idx - int(np.round(self._samplerate/freq[zc_idx])))
|
|
636
|
+
fund_amp, fund_phase = calc_single_freq(u_raw[local_start_idx:local_stop_idx], freq[zc_idx], self._samplerate)
|
|
637
|
+
frac_sidx_phi_offset = (wanted_pmu_ts_map[pmu_sidx] - real_pmu_ts_map[pmu_sidx])/1e6*2*np.pi*freq[zc_idx]
|
|
638
|
+
phase._calc_channels["pmu"]["voltage"]["rms"].put_data_single(pmu_sidx, fund_amp)
|
|
639
|
+
phase._calc_channels["pmu"]["voltage"]["phi"].put_data_single(pmu_sidx, pq.normalize_phi(np.rad2deg(fund_phase+np.pi/2+frac_sidx_phi_offset)))
|
|
640
|
+
# TODO: Add Frequency PMU Channel as well??
|
|
641
|
+
# TODO: Add current channels?
|
|
642
|
+
self._pmu_last_processed_sidx = stop_sidx
|
|
643
|
+
self._pmu_last_processed_ts_us = last_pmu_ts
|
|
644
|
+
|
|
574
645
|
def _detect_zero_crossings(self, start_acq_sidx: int, stop_acq_sidx: int) -> List[float]:
|
|
575
646
|
"""
|
|
576
647
|
Detects zero crossings in the signal.
|
|
@@ -601,13 +672,8 @@ class PowerSystem(object):
|
|
|
601
672
|
self._calculation_mode = "FALLBACK"
|
|
602
673
|
else:
|
|
603
674
|
self._calculation_mode = "NORMAL"
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
filtered_zero_crossings = []
|
|
607
|
-
for idx,zc in enumerate(zero_crossings):
|
|
608
|
-
if (int(zc)+start_acq_sidx) >= self._zero_crossings[-1] or np.isnan(self._zero_crossings[-1]):
|
|
609
|
-
filtered_zero_crossings.append(zc)
|
|
610
|
-
return filtered_zero_crossings
|
|
675
|
+
|
|
676
|
+
return zero_crossings
|
|
611
677
|
|
|
612
678
|
def get_aggregated_data(self, start_acq_sidx: int, stop_acq_sidx: int) -> dict:
|
|
613
679
|
"""
|
|
@@ -671,12 +737,13 @@ class PowerPhase(object):
|
|
|
671
737
|
Parameters:
|
|
672
738
|
features: Dict of features
|
|
673
739
|
"""
|
|
674
|
-
self._calc_channels = {"half_period": {}, "one_period": {}, "one_period_ovlp": {}, "multi_period": {}}
|
|
740
|
+
self._calc_channels = {"half_period": {}, "one_period": {}, "one_period_ovlp": {}, "multi_period": {}, "pmu": {}}
|
|
675
741
|
# Create Voltage Channels
|
|
676
742
|
self._calc_channels["half_period"]["voltage"] = {}
|
|
677
743
|
self._calc_channels["one_period"]["voltage"] = {}
|
|
678
744
|
self._calc_channels["one_period_ovlp"]["voltage"] = {}
|
|
679
745
|
self._calc_channels["multi_period"]["voltage"] = {}
|
|
746
|
+
self._calc_channels["pmu"]["voltage"] = {}
|
|
680
747
|
self._calc_channels["half_period"]["voltage"]["trms"] = DataChannelBuffer('U{:s}_hp_rms'.format(self.name), agg_type='rms', unit="V")
|
|
681
748
|
self._calc_channels["one_period"]["voltage"]["trms"] = DataChannelBuffer('U{:s}_1p_rms'.format(self.name), agg_type='rms', unit="V")
|
|
682
749
|
self._calc_channels["one_period"]["voltage"]["slope"] = DataChannelBuffer('U{:s}_1p_slope'.format(self.name), agg_type='max', unit="V/s")
|
|
@@ -706,6 +773,11 @@ class PowerPhase(object):
|
|
|
706
773
|
|
|
707
774
|
if "one_period_fundamental" in features and features["one_period_fundamental"] > 0:
|
|
708
775
|
self._calc_channels["one_period"]["voltage"]["fund_rms"] = DataChannelBuffer('U{:s}_1p_H1_rms'.format(self.name), agg_type='rms', unit="V")
|
|
776
|
+
self._calc_channels["one_period"]["voltage"]["fund_phi"] = DataChannelBuffer('U{:s}_1p_H1_phi'.format(self.name), agg_type='phi', unit="°")
|
|
777
|
+
|
|
778
|
+
if "pmu_calculation" in features and features["pmu_calculation"]:
|
|
779
|
+
self._calc_channels["pmu"]["voltage"]["rms"] = DataChannelBuffer('U{:s}_pmu_rms'.format(self.name), agg_type='rms', unit="V")
|
|
780
|
+
self._calc_channels["pmu"]["voltage"]["phi"] = DataChannelBuffer('U{:s}_pmu_phi'.format(self.name), agg_type='phi', unit="°")
|
|
709
781
|
|
|
710
782
|
# Create Current Channels
|
|
711
783
|
if self._i_channel:
|
|
@@ -713,6 +785,7 @@ class PowerPhase(object):
|
|
|
713
785
|
self._calc_channels["multi_period"]["current"] = {}
|
|
714
786
|
self._calc_channels["one_period"]["current"]["trms"] = DataChannelBuffer('I{:s}_1p_rms'.format(self.name), agg_type='rms', unit="A")
|
|
715
787
|
self._calc_channels["multi_period"]["current"]["trms"] = DataChannelBuffer('I{:s}_rms'.format(self.name), agg_type='rms', unit="A")
|
|
788
|
+
self._calc_channels["pmu"]["current"] = {}
|
|
716
789
|
self._calc_channels["one_period"]["power"] = {}
|
|
717
790
|
self._calc_channels["multi_period"]["power"] = {}
|
|
718
791
|
|
|
@@ -725,6 +798,10 @@ class PowerPhase(object):
|
|
|
725
798
|
self._calc_channels["multi_period"]["power"]['p_fund_mag'] = DataChannelBuffer('P{:s}_H1'.format(self.name), agg_type='mean', unit="W")
|
|
726
799
|
self._calc_channels["multi_period"]["power"]['q_fund_mag'] = DataChannelBuffer('Q{:s}_H1'.format(self.name), agg_type='mean', unit="var")
|
|
727
800
|
|
|
801
|
+
if "pmu_calculation" in features and features["pmu_calculation"]:
|
|
802
|
+
self._calc_channels["pmu"]["current"]["rms"] = DataChannelBuffer('I{:s}_pmu_rms'.format(self.name), agg_type='rms', unit="A")
|
|
803
|
+
self._calc_channels["pmu"]["current"]["phi"] = DataChannelBuffer('I{:s}_pmu_phi'.format(self.name), agg_type='phi', unit="°")
|
|
804
|
+
|
|
728
805
|
# Create Power Channels
|
|
729
806
|
self._calc_channels["one_period"]["power"]['p_avg'] = DataChannelBuffer('P{:s}_1p'.format(self.name), agg_type='mean', unit="W")
|
|
730
807
|
self._calc_channels["multi_period"]["power"]['p_avg'] = DataChannelBuffer('P{:s}'.format(self.name), agg_type='mean', unit="W")
|
pqopen/zcd.py
CHANGED
|
@@ -40,8 +40,7 @@ class ZeroCrossDetector:
|
|
|
40
40
|
self.samplerate = samplerate
|
|
41
41
|
|
|
42
42
|
# Design a Butterworth low-pass filter
|
|
43
|
-
|
|
44
|
-
self._filter_coeff = signal.iirfilter(2, normal_cutoff, btype='lowpass', ftype='butter')
|
|
43
|
+
self._filter_coeff = signal.iirfilter(2, self.f_cutoff, btype='lowpass', ftype='butter', fs=self.samplerate)
|
|
45
44
|
self._filter_zi = np.zeros(len(self._filter_coeff[0]) - 1)
|
|
46
45
|
|
|
47
46
|
self._last_filtered_sample = 0
|
|
@@ -49,11 +48,11 @@ class ZeroCrossDetector:
|
|
|
49
48
|
self._last_zc_p = None
|
|
50
49
|
self._last_zc_n = None
|
|
51
50
|
self._last_zc_n_val = None
|
|
51
|
+
self._last_zc = None
|
|
52
52
|
|
|
53
53
|
# Calculate the filter delay in samples
|
|
54
|
-
w, h = signal.freqz(self._filter_coeff[0], self._filter_coeff[1],
|
|
55
|
-
|
|
56
|
-
self.filter_delay_samples = np.angle(h)[0] / (2 * np.pi) * self.samplerate / self.f_cutoff
|
|
54
|
+
w, h = signal.freqz(self._filter_coeff[0], self._filter_coeff[1], worN=[self.f_cutoff], fs=self.samplerate)
|
|
55
|
+
self.filter_delay_samples = np.angle(h)[0] / (2 * np.pi) * self.samplerate / self.f_cutoff - 1 # due to adding sample in front for continuity
|
|
57
56
|
self.filtered_data = []
|
|
58
57
|
|
|
59
58
|
def process(self, data: np.ndarray)-> list:
|
|
@@ -113,9 +112,15 @@ class ZeroCrossDetector:
|
|
|
113
112
|
k = (y2 - y1) / (x2 - x1)
|
|
114
113
|
d = y1 - k * x1
|
|
115
114
|
real_zc = -d/k
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
if np.isnan(real_zc):
|
|
116
|
+
logger.warning("Detection Error: real_zc is NaN, ignoring")
|
|
117
|
+
else:
|
|
118
|
+
if self._last_zc and (real_zc <= self._last_zc):
|
|
119
|
+
logger.warning("Detected ZC before last one, ignoring")
|
|
120
|
+
else:
|
|
121
|
+
zero_crossings.append(real_zc + self.filter_delay_samples)
|
|
122
|
+
last_used_p_idx = p_idx
|
|
123
|
+
self._last_zc = real_zc
|
|
119
124
|
|
|
120
125
|
# Update the last negative threshold-crossing for the next block
|
|
121
126
|
if last_used_p_idx < len(threshold_p_cross) and len(threshold_n_cross) > 0:
|
|
@@ -128,4 +133,8 @@ class ZeroCrossDetector:
|
|
|
128
133
|
else:
|
|
129
134
|
self._last_zc_n = None
|
|
130
135
|
|
|
136
|
+
# Update Last Valid ZC Index
|
|
137
|
+
if self._last_zc:
|
|
138
|
+
self._last_zc -= len(data)
|
|
139
|
+
|
|
131
140
|
return zero_crossings
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pqopen-lib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: A power quality processing library for calculating parameters from waveform data
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaqOpen/pqopen-lib
|
|
6
6
|
Project-URL: Issues, https://github.com/DaqOpen/pqopen-lib/issues
|
|
@@ -3,10 +3,10 @@ pqopen/eventdetector.py,sha256=NKZU7GbeorZdkYu3ET4lhMaeynw70GhIGO2p1xH4aTA,11962
|
|
|
3
3
|
pqopen/goertzel.py,sha256=JhehuEj-xNnwQEh4Ti8dXiGvfDmxbdEoaxIsFswZNcM,2008
|
|
4
4
|
pqopen/helper.py,sha256=bM_wDck5OfeEy96U_2FjCwZuLZRPYyKVeiYD3-vzP6M,1761
|
|
5
5
|
pqopen/powerquality.py,sha256=Qwyaj7BQqQPRivei140mv-Leh6u9uIQzViKLOY7bHyw,17877
|
|
6
|
-
pqopen/powersystem.py,sha256=
|
|
6
|
+
pqopen/powersystem.py,sha256=5gZcZem_mNRqezjC67qlCePht6L8CraPtQNsjPgP7C4,48581
|
|
7
7
|
pqopen/storagecontroller.py,sha256=AhVaPgIh4CBKKMJm9Ja0dOjVd4dLppWprxeeg3vrmAk,31266
|
|
8
|
-
pqopen/zcd.py,sha256=
|
|
9
|
-
pqopen_lib-0.
|
|
10
|
-
pqopen_lib-0.
|
|
11
|
-
pqopen_lib-0.
|
|
12
|
-
pqopen_lib-0.
|
|
8
|
+
pqopen/zcd.py,sha256=jN4jkGgFNBWQWRebefLc1GjUKm1xdYQXav_ajYfkuo4,6054
|
|
9
|
+
pqopen_lib-0.8.1.dist-info/METADATA,sha256=XSvdYAXMzrjm_FH1qEB5QHoYfsBk3TDldRj-pqbuEGM,4780
|
|
10
|
+
pqopen_lib-0.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
pqopen_lib-0.8.1.dist-info/licenses/LICENSE,sha256=yhYwu9dioytbAvNQa0UBwaBVcALqiOoBViEs4HLW6aU,1064
|
|
12
|
+
pqopen_lib-0.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|