commit b472765d5fb50af8d5d24261fe6c0a4cb08a2d00 Author: ddnthemc Date: Thu Mar 20 18:06:58 2025 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e9e9c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.png +/Quattro.pdf +/Quattro.odg diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/load_data_from_xlsx.py b/load_data_from_xlsx.py new file mode 100644 index 0000000..f891ea6 --- /dev/null +++ b/load_data_from_xlsx.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from openpyxl import load_workbook +from pathlib import Path +from my_folders import source_file_folder +from dataclasses import dataclass, asdict +import matplotlib.pyplot as plt + + +@dataclass(frozen=True) +class Measure: + ts: float # time stamp + v: float + i: float + + +@dataclass(frozen=True) +class DischargePoint(Measure): + c: float # Capacity + + +column_indexes = { # columns indexes that we want to import: None means try to find the column + 'TimeStamp': 0, + 'Ibatt': None, + 'Vbatt': None, +} + + +def load_a_file(path: Path) ->list[Measure]: + """Returns a sorted list of Measures()""" + discharge = [] + wb_obj = load_workbook(path) + + # Get workbook active sheet object + # from the active attribute + sheet_obj = wb_obj.active + + header_row_already_seen = False + for row in sheet_obj.values: + if all([c is None for c in row]): + continue # Do skip empty lines + if not header_row_already_seen: + for idx, value in enumerate(row): + if isinstance(value, str): + for k in column_indexes: + if k.lower() == value.lower(): + column_indexes[k] = idx if k != 'Ibatt' else idx - 1 + header_row_already_seen = all([v is not None for v in column_indexes.values()]) + if header_row_already_seen: + print(column_indexes) + ts = row[column_indexes['TimeStamp']] + v = row[column_indexes['Vbatt']] + i = row[column_indexes['Ibatt']] + if None not in (ts, v, i): + i /= 0.010007 + measure = Measure(ts=ts, v=v, i=i) + # print(measure) + discharge.append(measure) + + discharge.sort(key=lambda x: x.ts) + return discharge + + +def compute_discharge(measures: list[Measure], initial_capacity: float = 0.0) -> list[DischargePoint]: + discharge_sequence = [] + last_m = None + t0 = None + for m in measures: + if t0 is None: + t0 = m.ts + if last_m is not None: + initial_capacity += (m.i + last_m.i) * 0.5 * (m.ts - last_m.ts) / 3600.0 + meas = asdict(m) + meas['ts'] -= t0 + discharge_sequence.append(DischargePoint(c=initial_capacity, **meas)) + last_m = m + return discharge_sequence + + +def show_a_discharge(discharge: list[DischargePoint], title='Title'): + out_name = f'{title}.png' + if '100_per' in title: + title = 'Battery Option: 100%' + elif '80_per' in title: + title = 'Battery Option: 80%' + else: + title = f'Battery Option: OFF ({title})' + how_many = 1 + fig, axs_maybe = plt.subplots(how_many, 1, tight_layout=False, figsize=(7.85, 5.38)) + # fig.suptitle(f'Recahrging Behavior', fontsize=20) + # fig, ax = plt.subplots(figsize=(11, 6)) + if how_many == 1: + axs = (axs_maybe,) + else: + axs = axs_maybe + ax = axs[0] + ax.grid(True) + axr = ax.twinx() + # ax.set_xlim(0.0, 125) + mins = [] + vbatt = [] + ibatt = [] + caps = [] + for dp in discharge: + mins.append(dp.ts/60.0) + vbatt.append(dp.v) + ibatt.append(dp.i * 1000.0) + caps.append(dp.c * 1000.0) + ax.plot(mins, vbatt, color='g', lw=3, alpha=0.5, label='V_Batt') + axr.plot(mins, caps, color='b', lw=3, alpha=0.5, label='Capacity') + axr.plot(mins, ibatt, color='r', lw=3, alpha=0.5, label='Current') + ax.set_xlim(0, None) + ax.set_ylim(3.0, 4.3) + axr.set_ylim(0, 1100) + axr.set_ylabel('Capacity [mAh] / Current [mA]') + ax.set_xlabel('time [minutes') + ax.set_ylabel('Cell Voltage [V]') + ax.set_title(title) + axr.legend(loc='center right') + ax.legend(loc='upper left') + txt = f'End Capacity: {caps[-1]:.1f} mAh' + print(txt) + axr.text(mins[-1], caps[-1] - 150, txt, ha='right', fontsize=10, bbox={'facecolor': 'cyan', 'alpha': 0.7, 'pad': 5}) + # fig.tight_layout() + fig.savefig(out_name, dpi=300) + plt.close() + + +if __name__ == '__main__': + for f in source_file_folder.iterdir(): + if f.is_file() and f.suffix.lower() == '.xlsx': + print(f) + measured_data = load_a_file(f) + discharge = compute_discharge(measured_data) + show_a_discharge(discharge, title=f.stem.replace(' ', '_')) + # break + # for d in discharge: + # print(d) diff --git a/my_folders.py b/my_folders.py new file mode 100644 index 0000000..d704ed0 --- /dev/null +++ b/my_folders.py @@ -0,0 +1,14 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +from pathlib import Path + + +source_file_folder = Path('C:/Mc/Salvalavita_s/SeniorPhones/Batterytests') + + +def main(): + pass + + +if __name__ == '__main__': + main()