diff options
Diffstat (limited to 'bin/plot-conversion-efficiency')
-rwxr-xr-x | bin/plot-conversion-efficiency | 216 |
1 files changed, 216 insertions, 0 deletions
diff --git a/bin/plot-conversion-efficiency b/bin/plot-conversion-efficiency new file mode 100755 index 0000000..b80e97f --- /dev/null +++ b/bin/plot-conversion-efficiency @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# vim:tabstop=4 softtabstop=4 shiftwidth=4 textwidth=160 smarttab expandtab colorcolumn=160 +# +# Copyright (C) 2023 Daniel Friesel +# +# SPDX-License-Identifier: GPL-2.0-or-later + +"""plot-conversion-efficiency - Show several conversion efficiency plots at once + +DESCRIPTION + +fixme + +OPTIONS +""" + +import argparse +import bisect +import numpy as np +from matplotlib import cm +import matplotlib.pyplot as plt + +matplotlib_theme = "fast" + + +def neighbouring_avg(data, timestamp, eps=0.1): + samples = list() + range_left = bisect.bisect_left(data[:, 0], timestamp - eps) + range_right = bisect.bisect_right(data[:, 0], timestamp + eps) + samples = data[range_left:range_right, 1] + if not len(samples): + return None + return np.mean(samples) + + +def load_korad(filename, skip=None, limit=None): + if filename.endswith(".xz"): + import lzma + + with lzma.open(filename, "rt") as f: + log_data = f.read() + else: + with open(filename, "r") as f: + log_data = f.read() + lines = log_data.split("\n") + data_count = sum(map(lambda x: len(x) > 0 and x[0] != "#", lines)) + data_lines = filter(lambda x: len(x) > 0 and x[0] != "#", lines) + + data = np.empty((data_count, 3)) + skip_index = 0 + limit_index = data_count + + for i, line in enumerate(data_lines): + fields = line.split() + if len(fields) == 3: + timestamp, voltage, current = map(float, fields) + elif len(fields) == 5: + timestamp, voltage, current, max_voltage, max_current = map(float, fields) + else: + raise RuntimeError('cannot parse line "{}"'.format(line)) + + if i == 0: + first_timestamp = timestamp + + timestamp = timestamp - first_timestamp + + if skip is not None and timestamp < skip: + skip_index = i + 1 + continue + + if limit is not None and timestamp > limit: + limit_index = i - 1 + break + + data[i] = [timestamp, current, voltage] + + data = data[skip_index:limit_index] + + return data + + +def load_ads1115(filename, in_channel, out_channel): + readings = list() + first_timestamp = None + with open(filename, "r") as f: + for line in f: + if line.startswith("#"): + continue + timestamp, channel, voltage = line.split() + timestamp = float(timestamp) + channel = int(channel) + voltage = float(voltage) + if abs(voltage) > 1: + if first_timestamp is None: + first_timestamp = timestamp + timestamp -= first_timestamp + readings.append((timestamp, channel, voltage)) + vin_t = list() + vout_t = list() + vout_vin = list() + + last_vin = None + last_vout = None + for timestamp, channel, voltage in readings: + if channel == in_channel: + vin_t.append((timestamp, voltage)) + if last_vin is not None and last_vout is not None: + vout_vin.append((np.mean((last_vin, voltage)), last_vout)) + last_vin = voltage + elif channel == out_channel: + vout_t.append((timestamp, voltage)) + if last_vin is not None and last_vout is not None: + vout_vin.append((last_vin, np.mean((last_vout, voltage)))) + last_vout = voltage + + return np.array(vin_t), np.array(vout_t), np.array(vout_vin) + + +def main(): + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__ + ) + parser.add_argument( + "--skip", + metavar="N", + type=float, + default=0, + help="Skip the first N seconds of data. This is useful to avoid startup code influencing the results of a long-running measurement", + ) + parser.add_argument( + "--limit", + type=float, + metavar="N", + help="Limit analysis to the first N seconds of data", + ) + parser.add_argument( + "--in-channel", + metavar="CHANNEL", + type=int, + default=0, + help="ADS1115 input channel", + ) + parser.add_argument( + "--out-channel", + metavar="CHANNEL", + type=int, + default=2, + help="ADS1115 output channel", + ) + parser.add_argument( + "--dark-mode", action="store_true", help="Show plots on a dark background" + ) + parser.add_argument("--title", type=str, help="Plot title") + parser.add_argument( + "files", + type=str, + nargs="+", + help="Pairs of <current[A]>:<korad filename>:<ads1115 filename>", + ) + + args = parser.parse_args() + + if args.dark_mode: + global matplotlib_theme + matplotlib_theme = "dark_background" + + cmap = cm.get_cmap("winter") + handles = list() + for i, pair in enumerate(args.files): + out_current, korad_file, ads1115_file = pair.split(":") + out_current = float(out_current) + iin_t = load_korad(korad_file, args.skip, args.limit) + vin_t, vout_t, vout_vin = load_ads1115( + ads1115_file, args.in_channel, args.out_channel + ) + + voltage_in = list() + power_in = list() + power_out = list() + + for timestamp, in_current, in_voltage in iin_t: + v_in = neighbouring_avg(vin_t, timestamp) + v_out = neighbouring_avg(vout_t, timestamp) + + if v_in is None or v_out is None: + continue + + voltage_in.append(v_in) + power_in.append(v_in * in_current) + power_out.append(v_out * out_current) + + voltage_in = np.array(voltage_in) + power_in = np.array(power_in) + power_out = np.array(power_out) + + (handle,) = plt.plot( + voltage_in, + power_out / power_in, + linestyle="none", + marker="s", + color=cmap(i / len(args.files)), + markersize=2, + label=f"{out_current:0.2f} A", + ) + handles.append(handle) + + plt.legend(handles=handles, title="Output Current") + if args.title: + plt.title(args.title) + plt.xlabel("Input Voltage [V]") + plt.ylabel("Conversion Efficiency [%]") + plt.show() + + +if __name__ == "__main__": + main() |