# /// script # requires-python = ">=3.11" # dependencies = ["numpy", "matplotlib"] # /// """Mostra o salva il grafico di T e ΔT di un CSV prodotto da genera.py.""" import argparse import re import sys from pathlib import Path import matplotlib import numpy as np CARTELLA_OUTPUT = Path("output") class ParserIt(argparse.ArgumentParser): """ArgumentParser con i messaggi d'errore più comuni in italiano (exit 2).""" TRADUZIONI = [ (r"the following arguments are required: (.*)", r"manca l'argomento obbligatorio: \1"), (r"unrecognized arguments: (.*)", r"argomenti non riconosciuti: \1"), (r"argument (.*): invalid int value: (.*)", r"argomento \1: \2 non è un numero intero"), (r"argument (.*): expected one argument", r"argomento \1: manca il valore"), ] def error(self, message): for pattern, sostituzione in self.TRADUZIONI: message = re.sub(pattern, sostituzione, message) self.print_usage(sys.stderr) print(f"{self.prog}: errore: {message}", file=sys.stderr) sys.exit(2) def errore(msg): print(f"Errore: {msg}", file=sys.stderr) sys.exit(1) def ultimo_csv(): csv = sorted(CARTELLA_OUTPUT.glob("*.csv"), key=lambda p: p.stat().st_mtime) if not csv: errore(f"nessun CSV in '{CARTELLA_OUTPUT}': genera prima i dati con genera.py") return csv[-1] def main(): ap = ParserIt( prog="grafico.py", usage="grafico.py [-h] [-o FILE] [csv]", description="Disegna T e dT = T - T_amb in funzione del tempo (due pannelli con asse comune)\n" "da un CSV prodotto da genera.py.", epilog="""\ esempi: grafico.py finestra con l'ultimo CSV modificato in output/ grafico.py output/gen_42.csv un CSV preciso grafico.py -o grafico.png salva l'immagine invece di aprire la finestra (utile senza schermo, es. via SSH) formato atteso del CSV: t_ms,T_C,dT_C (come scritto da genera.py). codici di uscita: 0 ok 1 errore, con messaggio in italiano su stderr: nessun CSV in output/, file illeggibile o con formato diverso, immagine non scrivibile 2 argomenti della riga di comando non validi dipendenze: numpy, matplotlib.""", formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False, ) argomenti = ap.add_argument_group("argomenti") opzioni = ap.add_argument_group("opzioni") opzioni.add_argument("-h", "--help", action="help", help="mostra questo aiuto ed esce") argomenti.add_argument("csv", nargs="?", type=Path, help="CSV da disegnare (default: l'ultimo modificato in output/)") opzioni.add_argument( "-o", "--output", type=Path, metavar="FILE", help="salva l'immagine in FILE invece di aprire la finestra; il formato segue l'estensione (png, pdf, svg)", ) args = ap.parse_args() percorso = args.csv or ultimo_csv() try: t_ms, T, dT = np.loadtxt(percorso, delimiter=",", skiprows=1, unpack=True) except OSError as e: errore(f"impossibile leggere '{percorso}': {e.strerror}") except ValueError: errore(f"'{percorso}' non ha il formato t_ms,T_C,dT_C") if args.output: matplotlib.use("Agg") import matplotlib.pyplot as plt fig, (ax_T, ax_dT) = plt.subplots(2, 1, sharex=True, figsize=(10, 6)) t_s = t_ms / 1000 ax_T.plot(t_s, T, lw=0.8) ax_T.set_ylabel("T [°C]") ax_dT.plot(t_s, dT, lw=0.8, color="tab:orange") ax_dT.set_ylabel("ΔT = T − T_amb [°C]") ax_dT.set_xlabel("tempo [s]") for ax in (ax_T, ax_dT): ax.grid(alpha=0.3) fig.suptitle(percorso.name) fig.tight_layout() if args.output: try: fig.savefig(args.output, dpi=150) except OSError as e: errore(f"impossibile scrivere '{args.output}': {e.strerror}") print(f"scritto: {args.output}", file=sys.stderr) else: plt.show() if __name__ == "__main__": main()