2026-10-29 08:56:08 +01:00
|
|
|
|
# /// 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
|
2026-10-29 09:37:25 +01:00
|
|
|
|
import re
|
2026-10-29 08:56:08 +01:00
|
|
|
|
import sys
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
import matplotlib
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
CARTELLA_OUTPUT = Path("output")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-10-29 09:37:25 +01:00
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-10-29 08:56:08 +01:00
|
|
|
|
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():
|
2026-10-29 09:37:25 +01:00
|
|
|
|
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)",
|
|
|
|
|
|
)
|
2026-10-29 08:56:08 +01:00
|
|
|
|
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()
|