Files
PiGarden/Python/cron_include.py
2025-07-09 18:43:25 +02:00

435 lines
18 KiB
Python

import os
import re
import logging
from crontab import CronTab, CronItem # Importa le classi necessarie da python-crontab
# --- Mock/Placeholder per le dipendenze esterne ---
# In un'applicazione reale, queste funzioni verrebbero fornite dal tuo sistema piGarden principale.
# Configura un logger di base per le funzioni di log
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def log_write(log_type, level, message):
"""
Simula la funzione log_write dal tuo script Bash.
In un'applicazione reale, useresti il modulo logging di Python.
"""
if level == "info":
logging.info(f"[{log_type}] {message}")
elif level == "warning":
logging.warning(f"[{log_type}] {message}")
elif level == "error":
logging.error(f"[{log_type}] {message}")
else:
logging.debug(f"[{log_type}] {message}")
def trigger_event(event_name, *args):
"""
Simula la funzione trigger_event dal tuo script Bash.
"""
log_write("event", "info", f"Triggered event: {event_name} with args: {args}")
# Qui potresti aggiungere la logica per chiamare handler di eventi reali
def alias_exists(alias_name):
"""
Simula la funzione alias_exists.
Dovrebbe essere integrata con la tua configurazione delle elettrovalvole.
Per questo esempio, restituisce True solo per alias "1" a "6".
"""
try:
num = int(alias_name)
return 1 <= num <= EV_TOTAL # Assumiamo EV_TOTAL sia definito globalmente o passato
except ValueError:
return False
# EV_TOTAL deve essere definito o passato, simuliamo un valore qui
EV_TOTAL = 6
# Percorso dello script principale (es. piGarden.py)
# Questo dovrebbe essere il percorso assoluto del tuo script piGarden principale
# In un'applicazione reale, lo passeresti dalla tua classe PiGarden
PI_GARDEN_SCRIPT_PATH = "/home/pi/piGarden/piGarden.py"
# --- Classe CronManager ---
class CronManager:
def __init__(self, script_path, ev_total_val, log_writer, event_trigger, alias_checker):
self.script_path = script_path
self.ev_total = ev_total_val
self.log_write = log_writer
self.trigger_event = event_trigger
self.alias_exists = alias_checker
self.cron_user = True # Gestisce il crontab dell'utente corrente
def _get_crontab(self):
"""Ottiene l'oggetto CronTab per l'utente corrente."""
try:
return CronTab(user=self.cron_user)
except Exception as e:
self.log_write("cron", "error", f"Impossibile accedere al crontab: {e}")
raise
def cron_del(self, cron_type, cron_arg=""):
"""
Elimina una tipologia di schedulazione dal crontab dell'utente.
:param cron_type: Tipologia del cron (es. "init", "open", "close").
:param cron_arg: Argomento della tipologia (es. alias dell'elettrovalvola).
"""
if not cron_type:
self.log_write("cron", "error", "Tipo cron vuoto")
print("Tipo cron vuoto", file=os.sys.stderr)
return False
crontab = self._get_crontab()
jobs_to_remove = []
# Il tuo script Bash usa commenti START/END.
# Possiamo cercare lavori che contengono questi commenti.
# Alternativamente, si potrebbe assegnare un commento specifico ad ogni job creato.
start_comment_pattern = re.compile(rf"^# START cron {re.escape(cron_type)} {re.escape(cron_arg)}$")
end_comment_pattern = re.compile(rf"^# END cron {re.escape(cron_type)} {re.escape(cron_arg)}$")
found_block = False
in_block = False
for job in list(crontab.jobs): # Iterate over a copy because we might modify
if start_comment_pattern.match(job.comment or ""):
in_block = True
found_block = True
jobs_to_remove.append(job) # Include the START comment job itself
elif end_comment_pattern.match(job.comment or ""):
if in_block:
jobs_to_remove.append(job) # Include the END comment job itself
in_block = False
elif in_block:
jobs_to_remove.append(job)
if not found_block:
print(f"{cron_type} {cron_arg} cron non presente", file=os.sys.stderr)
return True # Considerato un successo se non c'è nulla da eliminare
self.trigger_event("cron_del_before", cron_type, cron_arg)
for job in jobs_to_remove:
crontab.remove(job)
try:
crontab.write()
self.log_write("cron", "info", f"Cron '{cron_type} {cron_arg}' eliminato con successo.")
self.trigger_event("cron_del_after", cron_type, cron_arg)
return True
except Exception as e:
self.log_write("cron", "error", f"Errore durante la scrittura del crontab: {e}")
print(f"Errore durante la scrittura del crontab: {e}", file=os.sys.stderr)
return False
def _get_cron_command(self, cron_type, cron_arg, cron_arg2):
"""Determina il comando Bash da eseguire per il cron job."""
base_command = f"{self.script_path}"
if cron_type == "init":
return f"{base_command} init"
elif cron_type == "start_socket_server":
return f"{base_command} start_socket_server force"
elif cron_type == "check_rain_online":
return f"{base_command} check_rain_online 2> /tmp/check_rain_online.err"
elif cron_type == "check_rain_sensor":
return f"{base_command} check_rain_sensor 2> /tmp/check_rain_sensor.err"
elif cron_type == "close_all_for_rain":
return f"{base_command} close_all_for_rain 2> /tmp/close_all_for_rain.err 1> /dev/null"
elif cron_type == "open":
return f"{base_command} open {cron_arg}"
elif cron_type == "open_in":
return f"{base_command} open {cron_arg} {cron_arg2}"
elif cron_type == "open_in_stop":
return f"{base_command} close {cron_arg}"
elif cron_type == "close":
return f"{base_command} close {cron_arg}"
else:
self.log_write("cron", "error", f"Tipo cron errato: {cron_type}")
print(f"Tipo cron errato: {cron_type}", file=os.sys.stderr)
raise ValueError(f"Tipo cron errato: {cron_type}")
def cron_add(self, cron_type, minute="*", hour="*", dom="*", month="*", dow="*", cron_arg="", cron_arg2=""):
"""
Aggiunge una schedulazione nel crontab dell'utente.
:param cron_type: Tipologia del cron.
:param minute: Minuto (0-59, *, */n, @reboot).
:param hour: Ora (0-23, *, */n).
:param dom: Giorno del mese (1-31, *, */n).
:param month: Mese (1-12, *, */n).
:param dow: Giorno della settimana (0-6, *, */n).
:param cron_arg: Primo argomento specifico della tipologia.
:param cron_arg2: Secondo argomento specifico della tipologia (es. "disabled").
"""
if not cron_type:
self.log_write("cron", "error", "Tipo cron vuoto")
print("Tipo cron vuoto", file=os.sys.stderr)
return False
# Elimina prima qualsiasi blocco esistente per garantire l'idempotenza
self.cron_del(cron_type, cron_arg)
crontab = self._get_crontab()
# Determina il comando e se deve essere disabilitato
cron_command = self._get_cron_command(cron_type, cron_arg, cron_arg2)
cron_disabled = (cron_arg2 == "disabled")
# Crea i commenti START e END per il blocco
start_comment = f"# START cron {cron_type} {cron_arg}"
end_comment = f"# END cron {cron_type} {cron_arg}"
# Aggiungi il commento START
job_start = crontab.new(command=f"echo '{start_comment}'", comment=start_comment)
job_start.minute.every(1) # Un cron job fittizio per il commento START
job_start.enabled = False # Disabilita il job commento
# Aggiungi il job principale
job = crontab.new(command=cron_command)
if minute == "@reboot":
job.set_every("reboot")
else:
job.minute.on(minute)
job.hour.on(hour)
job.dom.on(dom)
job.month.on(month)
job.dow.on(dow)
job.enabled = not cron_disabled
job.comment = f"piGarden {cron_type} {cron_arg}" # Un commento più descrittivo per il job reale
# Aggiungi il commento END
job_end = crontab.new(command=f"echo '{end_comment}'", comment=end_comment)
job_end.minute.every(1) # Un cron job fittizio per il commento END
job_end.enabled = False # Disabilita il job commento
try:
crontab.write()
self.log_write("cron", "info", f"Cron '{cron_type} {cron_arg}' aggiunto con successo: {job.render()}")
self.trigger_event("cron_add_after", cron_type, cron_arg, job.render())
return True
except Exception as e:
self.log_write("cron", "error", f"Errore durante la scrittura del crontab: {e}")
print(f"Errore durante la scrittura del crontab: {e}", file=os.sys.stderr)
return False
def cron_get(self, cron_type, cron_arg=""):
"""
Legge una tipologia di schedulazione dal crontab dell'utente.
:param cron_type: Tipologia del cron.
:param cron_arg: Argomento della tipologia.
:return: Stringa contenente le schedulazioni trovate, separate da newline.
"""
if not cron_type:
self.log_write("cron", "error", "Tipo cron vuoto")
print("Tipo cron vuoto", file=os.sys.stderr)
return ""
crontab = self._get_crontab()
found_jobs = []
# Cerca i job principali che corrispondono al tipo e all'argomento
for job in crontab.jobs:
if job.comment and job.comment.startswith(f"piGarden {cron_type} {cron_arg}"):
found_jobs.append(job.render())
return "\n".join(found_jobs)
# --- Funzioni wrapper per tipi di cron specifici ---
def set_cron_init(self):
self.cron_del("init") # Assicurati che non ci siano duplicati
self.cron_add("init", minute="@reboot")
def del_cron_init(self):
self.cron_del("init")
def set_cron_start_socket_server(self):
self.cron_del("start_socket_server")
self.cron_add("start_socket_server", minute="@reboot")
def del_cron_start_socket_server(self):
self.cron_del("start_socket_server")
def set_cron_check_rain_sensor(self):
self.cron_del("check_rain_sensor")
self.cron_add("check_rain_sensor", minute="*") # Ogni minuto
def del_cron_check_rain_sensor(self):
self.cron_del("check_rain_sensor")
def set_cron_check_rain_online(self):
self.cron_del("check_rain_online")
self.cron_add("check_rain_online", minute="*/3") # Ogni 3 minuti
def del_cron_check_rain_online(self):
self.cron_del("check_rain_online")
def set_cron_close_all_for_rain(self):
self.cron_del("close_all_for_rain")
self.cron_add("close_all_for_rain", minute="*/5") # Ogni 5 minuti
def del_cron_close_all_for_rain(self):
self.cron_del("close_all_for_rain")
def add_cron_open(self, alias, minute, hour, dom, month, dow, disabled=""):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return False
self.cron_add("open", minute, hour, dom, month, dow, alias, disabled)
return True
def del_cron_open(self, alias):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return False
self.cron_del("open", alias)
return True
def get_cron_open(self, alias):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return ""
return self.cron_get("open", alias)
def del_cron_open_in(self, alias):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return False
self.cron_del("open_in", alias)
self.cron_del("open_in_stop", alias)
return True
def get_cron_close(self, alias):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return ""
return self.cron_get("close", alias)
def add_cron_close(self, alias, minute, hour, dom, month, dow, disabled=""):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return False
self.cron_add("close", minute, hour, dom, month, dow, alias, disabled)
return True
def del_cron_close(self, alias):
if not self.alias_exists(alias):
self.log_write("cron", "error", f"Alias '{alias}' non trovato")
print(f"Alias '{alias}' non trovato", file=os.sys.stderr)
return False
self.cron_del("close", alias)
return True
def cron_disable_all_open_close(self):
crontab = self._get_crontab()
for i in range(1, self.ev_total + 1):
alias = str(i) # Assumendo che gli alias siano i numeri delle EV
# Disabilita le schedulazioni di apertura
for job in list(crontab.jobs):
if job.comment and job.comment.startswith(f"piGarden open {alias}") and job.enabled:
job.enabled = False
self.log_write("cron", "info", f"Disabilitato cron 'open' per alias {alias}: {job.render()}")
# Disabilita le schedulazioni di chiusura
for job in list(crontab.jobs):
if job.comment and job.comment.startswith(f"piGarden close {alias}") and job.enabled:
job.enabled = False
self.log_write("cron", "info", f"Disabilitato cron 'close' per alias {alias}: {job.render()}")
try:
crontab.write()
self.log_write("cron", "info", "Tutte le schedulazioni di apertura/chiusura disabilitate.")
return True
except Exception as e:
self.log_write("cron", "error", f"Errore durante la disabilitazione dei cron: {e}")
return False
def cron_enable_all_open_close(self):
crontab = self._get_crontab()
for i in range(1, self.ev_total + 1):
alias = str(i) # Assumendo che gli alias siano i numeri delle EV
# Abilita le schedulazioni di apertura
for job in list(crontab.jobs):
if job.comment and job.comment.startswith(f"piGarden open {alias}") and not job.enabled:
job.enabled = True
self.log_write("cron", "info", f"Abilitato cron 'open' per alias {alias}: {job.render()}")
# Abilita le schedulazioni di chiusura
for job in list(crontab.jobs):
if job.comment and job.comment.startswith(f"piGarden close {alias}") and not job.enabled:
job.enabled = True
self.log_write("cron", "info", f"Abilitato cron 'close' per alias {alias}: {job.render()}")
try:
crontab.write()
self.log_write("cron", "info", "Tutte le schedulazioni di apertura/chiusura abilitate.")
return True
except Exception as e:
self.log_write("cron", "error", f"Errore durante l'abilitazione dei cron: {e}")
return False
# --- Esempio di utilizzo (per testare la classe CronManager) ---
if __name__ == "__main__":
# Inizializza il gestore cron con le dipendenze mock
cron_manager = CronManager(
script_path=PI_GARDEN_SCRIPT_PATH,
ev_total_val=EV_TOTAL,
log_writer=log_write,
event_trigger=trigger_event,
alias_checker=alias_exists
)
print("--- Test Cron Manager ---")
# Esempio: Aggiungi un cron per l'inizializzazione
print("\nAggiungo cron 'init'...")
cron_manager.set_cron_init()
# Esempio: Aggiungi un cron per aprire l'elettrovalvola "1" ogni giorno alle 7:00
print("\nAggiungo cron 'open' per EV 1 alle 07:00...")
cron_manager.add_cron_open("1", "0", "7", "*", "*", "*")
# Esempio: Aggiungi un cron per chiudere l'elettrovalvola "2" ogni 5 minuti (disabilitato)
print("\nAggiungo cron 'close' per EV 2 ogni 5 minuti (disabilitato)...")
cron_manager.add_cron_close("2", "*/5", "*", "*", "*", "*", "disabled")
# Esempio: Ottieni i cron per l'elettrovalvola "1"
print("\nCron 'open' per EV 1:")
print(cron_manager.get_cron_open("1"))
# Esempio: Ottieni i cron per l'elettrovalvola "2"
print("\nCron 'close' per EV 2:")
print(cron_manager.get_cron_close("2"))
# Esempio: Disabilita tutti i cron di apertura/chiusura
print("\nDisabilito tutti i cron di apertura/chiusura...")
cron_manager.cron_disable_all_open_close()
# Verifica lo stato dopo la disabilitazione
print("\nCron 'open' per EV 1 dopo disabilitazione:")
print(cron_manager.get_cron_open("1")) # Dovrebbe mostrare il job ma disabilitato
# Esempio: Abilita tutti i cron di apertura/chiusura
print("\nAbilito tutti i cron di apertura/chiusura...")
cron_manager.cron_enable_all_open_close()
# Verifica lo stato dopo l'abilitazione
print("\nCron 'open' per EV 1 dopo abilitazione:")
print(cron_manager.get_cron_open("1")) # Dovrebbe mostrare il job abilitato
# Esempio: Elimina un cron specifico
print("\nElimino cron 'init'...")
cron_manager.del_cron_init()
print("\nElimino cron 'open' per EV 1...")
cron_manager.del_cron_open("1")
print("\nElimino cron 'close' per EV 2...")
cron_manager.del_cron_close("2")
print("\n--- Test Completato ---")
print("Controlla il tuo crontab con 'crontab -l' per vedere le modifiche.")