1617 lines
72 KiB
Python
1617 lines
72 KiB
Python
import os
|
|
import sys
|
|
import time
|
|
import subprocess
|
|
import json
|
|
import re
|
|
import hashlib
|
|
import datetime
|
|
import shutil # Per rm -f e rmdir ricorsivo
|
|
import socket # Per il client MQTT (se non usi paho-mqtt)
|
|
|
|
# Per list_descendants
|
|
try:
|
|
import psutil
|
|
except ImportError:
|
|
print("Warning: psutil not found. Process descendant listing will be limited.", file=sys.stderr)
|
|
psutil = None
|
|
|
|
# --- CONFIGURAZIONE ---
|
|
# Questa classe simula il caricamento da /etc/piGarden.conf
|
|
# Dovrai popolare questi valori con la tua configurazione reale.
|
|
class Config:
|
|
def __init__(self):
|
|
# Versione dello script
|
|
self.VERSION = 0
|
|
self.SUB_VERSION = 6
|
|
self.RELEASE_VERSION = 5
|
|
|
|
# Percorsi e file
|
|
self.DIR_SCRIPT = os.path.dirname(os.path.abspath(__file__))
|
|
self.NAME_SCRIPT = os.path.basename(os.path.abspath(__file__))
|
|
self.CONFIG_ETC = "/etc/piGarden.conf" # Il percorso del file di configurazione Bash originale
|
|
|
|
# Percorso temporaneo (come in Bash, preferisce /run/shm)
|
|
self.TMP_PATH = "/run/shm" if os.path.isdir("/run/shm") else "/tmp"
|
|
|
|
self.TCPSERVER_PID_FILE = os.path.join(self.TMP_PATH, "piGardenTcpServer.pid")
|
|
self.LOCK_FILE = os.path.join(self.TMP_PATH, "piGarden.dir.lock")
|
|
self.TMP_CRON_FILE = os.path.join(self.TMP_PATH, f"pigarden.user.cron.{os.getpid()}") # PID dinamico
|
|
|
|
# Log
|
|
self.LOG_FILE = "/var/log/piGarden.log" # Esempio
|
|
self.LOG_OUTPUT_DRV_FILE = "/dev/null" # Default, come in Bash
|
|
self.LOG_FILE_MAX_SIZE = 10 * 1024 * 1024 # 10 MB, esempio
|
|
self.LOG_URL = "" # URL per inviare i log (se LOG_API_TOKEN è impostato)
|
|
self.LOG_API_TOKEN = ""
|
|
self.LOG_CURL_PARAM = "-s -k" # Parametri per curl (es. -s per silenzioso, -k per ignorare SSL)
|
|
|
|
# Stato delle elettrovalvole
|
|
self.STATUS_DIR = os.path.join(self.TMP_PATH, "pigarden_status") # Directory per gli stati delle EV
|
|
os.makedirs(self.STATUS_DIR, exist_ok=True) # Assicurati che la directory esista
|
|
|
|
# Elettrovalvole (esempi, devi popolarli con i tuoi dati reali)
|
|
# EV_TOTAL deve essere impostato in base a quante EV hai
|
|
self.EV_TOTAL = 2 # Esempio: 2 elettrovalvole
|
|
self.EV_MONOSTABLE = 0 # 0 per bistabile, 1 per monostabile (globale)
|
|
|
|
# Dettagli per ogni elettrovalvola (simula EVx_ALIAS, EVx_GPIO, EVx_NORAIN, EVx_REMOTE, EVx_MONOSTABLE)
|
|
self.EV_CONFIG = {
|
|
1: {"ALIAS": "Zona_1", "GPIO": 17, "NORAIN": 0, "REMOTE": 0, "MONOSTABLE": 0},
|
|
2: {"ALIAS": "Zona_2", "GPIO": 27, "NORAIN": 1, "REMOTE": 0, "MONOSTABLE": 0},
|
|
# Aggiungi altre elettrovalvole qui
|
|
}
|
|
|
|
# Sensori (esempi)
|
|
self.SENSOR_TOTAL = 0 # Esempio: 0 sensori
|
|
self.SENSOR_CONFIG = {
|
|
# 1: {"ALIAS": "Sensore_Umidita_1", "GPIO": 22, "TYPE": "moisture"},
|
|
# Aggiungi altri sensori qui
|
|
}
|
|
self.SENSOR_STATE_TYPE = "moisture temperature fertility illuminance" # Tipi di sensori supportati
|
|
|
|
self.RAIN_GPIO = None # GPIO per il sensore di pioggia (es. 23)
|
|
self.NOT_IRRIGATE_IF_RAIN_ONLINE = 0 # Secondi dopo l'ultima pioggia online per non irrigare
|
|
self.NOT_IRRIGATE_IF_RAIN_SENSOR = 0 # Secondi dopo l'ultima pioggia sensore per non irrigare
|
|
|
|
# MQTT
|
|
self.MQTT_ENABLE = 0 # 1 per abilitare MQTT
|
|
self.MQTT_HOST = "localhost"
|
|
self.MQTT_PORT = 1883
|
|
self.MQTT_USER = ""
|
|
self.MQTT_PWD = ""
|
|
self.MQTT_CLIENT_ID = "piGarden"
|
|
self.MQTT_TOPIC = "piGarden/status"
|
|
|
|
# piGardenSched
|
|
self.PIGARDENSCHED_PATH = "/usr/local/bin/piGardenSched" # Percorso dell'eseguibile piGardenSched
|
|
self.PIGARDENSCHED = 0 # 1 se piGardenSched è configurato e eseguibile
|
|
if os.path.exists(self.PIGARDENSCHED_PATH) and os.access(self.PIGARDENSCHED_PATH, os.X_OK):
|
|
self.PIGARDENSCHED = 1
|
|
|
|
# Statistiche di utilizzo
|
|
self.NO_SEND_IDENTIFIER = 0 # 1 per disabilitare l'invio dell'identificativo
|
|
|
|
# Variabili di stato interne (equivalenti a MESSAGE_INFO, CURRENT_EVENT, ecc. in Bash)
|
|
self.MESSAGE_INFO = ""
|
|
self.MESSAGE_WARNING = ""
|
|
self.MESSAGE_SUCCESS = ""
|
|
self.CURRENT_EVENT = ""
|
|
self.CURRENT_EVENT_ALIAS = ""
|
|
self.PARENT_PID = 0 # Usato per json_status quando chiamato da un processo figlio
|
|
|
|
# Servizio meteo online
|
|
self.WEATHER_SERVICE = "drv:wunderground" # o "none" o "drv:your_service"
|
|
# self.WEATHER_API_KEY = "YOUR_API_KEY" # Chiave API per il servizio meteo
|
|
# self.WEATHER_LOCATION = "YOUR_LOCATION" # Località per il servizio meteo
|
|
|
|
# Inizializza la configurazione globale
|
|
config = Config()
|
|
|
|
# --- FUNZIONI UTILITY GLOBALI ---
|
|
|
|
def log_write(source, level, message):
|
|
"""
|
|
Scrive un messaggio nel file di log e gestisce la rotazione.
|
|
Invia anche il log a piGardenWeb se configurato.
|
|
"""
|
|
if not os.path.exists(config.LOG_FILE):
|
|
try:
|
|
os.makedirs(os.path.dirname(config.LOG_FILE), exist_ok=True)
|
|
with open(config.LOG_FILE, 'a'): # Crea il file se non esiste
|
|
pass
|
|
except OSError as e:
|
|
print(f"Error creating log directory/file: {e}", file=sys.stderr)
|
|
return
|
|
|
|
# Gestione rotazione log
|
|
try:
|
|
if os.path.exists(config.LOG_FILE):
|
|
actual_size = os.path.getsize(config.LOG_FILE)
|
|
if actual_size >= config.LOG_FILE_MAX_SIZE:
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M")
|
|
shutil.move(config.LOG_FILE, f"{config.LOG_FILE}.{timestamp}")
|
|
# Non comprimiamo con gzip qui, lo script Bash lo faceva dopo lo spostamento
|
|
# Se vuoi gzip, puoi aggiungere: subprocess.run(["gzip", f"{config.LOG_FILE}.{timestamp}"])
|
|
log_write("general", "info", f"Log file rotated to {config.LOG_FILE}.{timestamp}")
|
|
except Exception as e:
|
|
print(f"Error during log rotation: {e}", file=sys.stderr)
|
|
|
|
# Scrivi sul file di log
|
|
timestamp_log = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
log_entry = f"{timestamp_log}\t\t{source}\t{level}\t{message}\n"
|
|
try:
|
|
with open(config.LOG_FILE, "a") as f:
|
|
f.write(log_entry)
|
|
except IOError as e:
|
|
print(f"Error writing to log file {config.LOG_FILE}: {e}", file=sys.stderr)
|
|
|
|
# Invia a piGardenWeb (se configurato)
|
|
log_send(source, level, timestamp_log, message)
|
|
|
|
def log_send(log_type, level, datetime_log, message):
|
|
"""Invia un log verso piGardenWeb."""
|
|
if config.LOG_URL and config.LOG_API_TOKEN:
|
|
try:
|
|
# Usiamo requests per un'interazione HTTP più robusta
|
|
# pip install requests
|
|
import requests
|
|
data = {
|
|
"api_token": config.LOG_API_TOKEN,
|
|
"type": log_type,
|
|
"level": level,
|
|
"datetime_log": datetime_log,
|
|
"message": message
|
|
}
|
|
# subprocess.Popen per non bloccare il processo principale
|
|
# In un'applicazione reale, useresti un thread separato o una coda di messaggi
|
|
# per inviare log in background per non bloccare l'esecuzione.
|
|
# Qui simulo il comportamento di `curl ... &`
|
|
|
|
# Nota: requests.post blocca. Per non bloccare, dovresti usare un thread.
|
|
# Per emulare `&> /dev/null &` di Bash, useremmo subprocess.Popen
|
|
# con stdout/stderr a DEVNULL e un thread per la chiamata requests.
|
|
# Per semplicità, qui faremo una chiamata bloccante, ma con un timeout.
|
|
|
|
# Esempio con subprocess.Popen per emulare curl in background
|
|
# Questo è più vicino al comportamento Bash originale
|
|
cmd = ["curl"] + config.LOG_CURL_PARAM.split() + [config.LOG_URL, "-d",
|
|
f"api_token={config.LOG_API_TOKEN}&type={log_type}&level={level}&datetime_log={datetime_log}&message={message}"]
|
|
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
except ImportError:
|
|
log_write("log_send", "warning", "requests module not found, cannot send logs via HTTP.")
|
|
except Exception as e:
|
|
log_write("log_send", "error", f"Error sending log to piGardenWeb: {e}")
|
|
|
|
def message_write(msg_type, message):
|
|
"""Scrive una tipologia di messaggio da inviare via socket server (o memorizza in variabili globali)."""
|
|
if msg_type == 'info':
|
|
config.MESSAGE_INFO = message
|
|
elif msg_type == "warning":
|
|
config.MESSAGE_WARNING = message
|
|
elif msg_type == "success":
|
|
config.MESSAGE_SUCCESS = message
|
|
else:
|
|
return
|
|
log_write("message_write", msg_type, message) # Anche i messaggi vengono loggati
|
|
|
|
def json_error(code, description):
|
|
"""Mostra un json per una risposta di errore."""
|
|
return json.dumps({"error": {"code": code, "description": description}})
|
|
|
|
# --- FUNZIONI DI GESTIONE DEL LOCK ---
|
|
def lock(current_time=0):
|
|
"""Gestisce l'apertura di un lock (file-based)."""
|
|
max_time = 10 # Secondi
|
|
|
|
if current_time > max_time:
|
|
log_write("general", "error", "Maximum locked time reached")
|
|
time.sleep(max_time) # Sleep prima di sbloccare e uscire
|
|
unlock()
|
|
sys.exit(1)
|
|
|
|
try:
|
|
os.mkdir(config.LOCK_FILE)
|
|
# Lock acquisito
|
|
except FileExistsError:
|
|
log_write("general", "info", f"Sleep 1 second for locked state (attempt {current_time + 1})")
|
|
time.sleep(1)
|
|
lock(current_time + 1) # Riprova ricorsivamente
|
|
except OSError as e:
|
|
log_write("general", "error", f"Error creating lock directory {config.LOCK_FILE}: {e}")
|
|
sys.exit(1)
|
|
|
|
def unlock():
|
|
"""Chiude un lock (file-based)."""
|
|
try:
|
|
os.rmdir(config.LOCK_FILE)
|
|
except OSError as e:
|
|
log_write("general", "error", f"Error removing lock directory {config.LOCK_FILE}: {e}")
|
|
|
|
# --- FUNZIONI DI GESTIONE DELLE ELETTROVALVOLE (EV) ---
|
|
|
|
def ev_set_state(ev_number, state):
|
|
"""Imposta lo stato di una elettrovalvola."""
|
|
state_file = os.path.join(config.STATUS_DIR, f"ev{ev_number}")
|
|
try:
|
|
with open(state_file, "w") as f:
|
|
f.write(str(state))
|
|
except IOError as e:
|
|
log_write("ev_set_state", "error", f"Error writing EV state to {state_file}: {e}")
|
|
|
|
def ev_get_state(ev_number):
|
|
"""Legge lo stato di una elettrovalvola."""
|
|
state_file = os.path.join(config.STATUS_DIR, f"ev{ev_number}")
|
|
if os.path.exists(state_file):
|
|
try:
|
|
with open(state_file, "r") as f:
|
|
return int(f.read().strip())
|
|
except (IOError, ValueError) as e:
|
|
log_write("ev_get_state", "error", f"Error reading EV state from {state_file}: {e}")
|
|
return 0 # Default a chiuso in caso di errore
|
|
return 0 # Default a chiuso se il file non esiste
|
|
|
|
def ev_alias2number(alias):
|
|
"""Recupera il numero di un'elettrovalvola in base all'alias."""
|
|
for num, ev_data in config.EV_CONFIG.items():
|
|
if ev_data["ALIAS"] == alias:
|
|
return num
|
|
log_write("general", "error", f"ERROR solenoid alias not found: {alias}")
|
|
message_write("warning", "Solenoid alias not found")
|
|
json_status_wrapper() # Chiama la funzione per stampare JSON
|
|
sys.exit(1) # Esce come faceva lo script Bash
|
|
|
|
def alias_exists(alias):
|
|
"""Verifica se un alias di un'elettrovalvola esiste."""
|
|
for ev_data in config.EV_CONFIG.values():
|
|
if ev_data["ALIAS"] == alias:
|
|
return True
|
|
return False
|
|
|
|
def ev_number2gpio(ev_number):
|
|
"""Recupera il numero di gpio associato a un'elettrovalvola."""
|
|
return config.EV_CONFIG.get(ev_number, {}).get("GPIO")
|
|
|
|
def ev_number2norain(ev_number):
|
|
"""Recupera il valore norain associato a un'elettrovalvola."""
|
|
return config.EV_CONFIG.get(ev_number, {}).get("NORAIN", 0) # Default a 0 se non specificato
|
|
|
|
def ev_number2remote(ev_number):
|
|
"""Recupera il valore remote associato a un'elettrovalvola."""
|
|
return config.EV_CONFIG.get(ev_number, {}).get("REMOTE", 0)
|
|
|
|
def ev_number2monostable(ev_number):
|
|
"""Recupera il valore monostable associato a un'elettrovalvola."""
|
|
return config.EV_CONFIG.get(ev_number, {}).get("MONOSTABLE", 0)
|
|
|
|
def ev_status_all():
|
|
"""Mostra lo stato di tutte le elettrovalvole."""
|
|
for i in range(1, config.EV_TOTAL + 1):
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS", f"EV{i}")
|
|
state = ev_get_state(i)
|
|
print(f"{alias}: {state}")
|
|
|
|
def ev_status(alias):
|
|
"""Mostra lo stato di un'elettrovalvola."""
|
|
ev_num = ev_alias2number(alias)
|
|
state = ev_get_state(ev_num)
|
|
print(f"{state}") # Stampa solo lo stato, come in Bash
|
|
return state
|
|
|
|
def close_all(force=False):
|
|
"""Chiude tutte le elettrovalvole."""
|
|
for i in range(1, config.EV_TOTAL + 1):
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS", f"EV{i}")
|
|
state = ev_get_state(i)
|
|
if state > 0 or force:
|
|
ev_close(alias)
|
|
log_write("irrigate", "info", f"close_all - Close solenoid '{alias}' for rain")
|
|
|
|
def list_alias():
|
|
"""Stampa la lista degli alias delle elettrovalvole."""
|
|
for ev_data in config.EV_CONFIG.values():
|
|
print(ev_data["ALIAS"])
|
|
|
|
# --- FUNZIONI DRIVER (PLACEHOLDERS) ---
|
|
# Queste funzioni simulano le interazioni hardware.
|
|
# DEVONO ESSERE IMPLEMENTATE CON LE LIBRERIE GPIO REALI (es. RPi.GPIO, gpiozero)
|
|
|
|
def drv_init():
|
|
"""Inizializza i driver GPIO."""
|
|
log_write("drv", "info", "Initializing GPIO drivers...")
|
|
# Esempio:
|
|
# import RPi.GPIO as GPIO
|
|
# GPIO.setmode(GPIO.BCM)
|
|
# GPIO.setwarnings(False)
|
|
|
|
def drv_supply_bistable_init(gpio1, gpio2):
|
|
"""Inizializza l'alimentazione per elettrovalvole bistabili."""
|
|
log_write("drv", "info", f"Initializing bistable supply on GPIOs {gpio1}, {gpio2}")
|
|
# Imposta i GPIO come output e a stato iniziale (es. LOW)
|
|
|
|
def drv_rele_init(gpio):
|
|
"""Inizializza un GPIO per un relè (elettrovalvola)."""
|
|
log_write("drv", "info", f"Initializing relay on GPIO {gpio}")
|
|
# Imposta il GPIO come output
|
|
|
|
def drv_rele_close(gpio):
|
|
"""Attiva il relè per chiudere (o aprire, a seconda della logica del relè/valvola)."""
|
|
log_write("drv", "info", f"Activating relay (close) on GPIO {gpio}")
|
|
# Esempio: GPIO.output(gpio, GPIO.HIGH) # O LOW, a seconda del relè
|
|
return 0 # 0 per successo
|
|
|
|
def drv_rele_open(gpio):
|
|
"""Disattiva il relè per aprire (o chiudere, a seconda della logica)."""
|
|
log_write("drv", "info", f"Deactivating relay (open) on GPIO {gpio}")
|
|
# Esempio: GPIO.output(gpio, GPIO.LOW) # O HIGH
|
|
return 0 # 0 per successo
|
|
|
|
def drv_supply_positive(gpio1, gpio2):
|
|
"""Imposta l'alimentazione con voltaggio positivo per bistabili."""
|
|
log_write("drv", "info", f"Setting positive supply on GPIOs {gpio1}, {gpio2}")
|
|
# Logica per invertire la polarità
|
|
|
|
def drv_supply_negative(gpio1, gpio2):
|
|
"""Imposta l'alimentazione con voltaggio negativo per bistabili."""
|
|
log_write("drv", "info", f"Setting negative supply on GPIOs {gpio1}, {gpio2}")
|
|
# Logica per invertire la polarità
|
|
|
|
def drv_rain_sensor_init(gpio):
|
|
"""Inizializza il sensore di pioggia."""
|
|
log_write("drv", "info", f"Initializing rain sensor on GPIO {gpio}")
|
|
# Imposta il GPIO come input e aggiungi un event listener
|
|
|
|
# --- FUNZIONI CRON (PLACEHOLDERS) ---
|
|
# Queste funzioni simulano l'interazione con crontab.
|
|
# Dovrai implementarle usando una libreria come `python-crontab` o manipolando il crontab.
|
|
|
|
def cron_del(cron_type, alias):
|
|
"""Elimina le voci cron per un tipo e alias specifici."""
|
|
log_write("cron", "info", f"Deleting cron entries for type: {cron_type}, alias: {alias}")
|
|
# Esempio: usa python-crontab per rimuovere le entry
|
|
return "" # Simula output vuoto per successo
|
|
|
|
def cron_add(cron_type, cron_schedule, alias, force=""):
|
|
"""Aggiunge una voce cron."""
|
|
log_write("cron", "info", f"Adding cron entry for type: {cron_type}, schedule: {cron_schedule}, alias: {alias}, force: {force}")
|
|
# Esempio: usa python-crontab per aggiungere l'entry
|
|
return ""
|
|
|
|
def cron_get(cron_type, alias):
|
|
"""Recupera le voci cron per un tipo e alias specifici."""
|
|
log_write("cron", "info", f"Getting cron entries for type: {cron_type}, alias: {alias}")
|
|
# Esempio: restituisce una stringa con le voci cron, separate da newline
|
|
return ""
|
|
|
|
def cron_disable_all_open_close():
|
|
"""Disabilita tutte le schedulazioni cron di apertura/chiusura."""
|
|
log_write("cron", "info", "Disabling all open/close cron schedules.")
|
|
return ""
|
|
|
|
def cron_enable_all_open_close():
|
|
"""Abilita tutte le schedulazioni cron di apertura/chiusura."""
|
|
log_write("cron", "info", "Enabling all open/close cron schedules.")
|
|
return ""
|
|
|
|
def set_cron_init():
|
|
"""Imposta il crontab per l'inizializzazione dell'unità di controllo."""
|
|
log_write("cron", "info", "Setting cron for init.")
|
|
return ""
|
|
|
|
def del_cron_init():
|
|
"""Rimuove il crontab per l'inizializzazione dell'unità di controllo."""
|
|
log_write("cron", "info", "Deleting cron for init.")
|
|
return ""
|
|
|
|
def set_cron_start_socket_server():
|
|
"""Imposta il crontab per l'avvio del socket server."""
|
|
log_write("cron", "info", "Setting cron for socket server start.")
|
|
return ""
|
|
|
|
def del_cron_start_socket_server():
|
|
"""Rimuove il crontab per l'avvio del socket server."""
|
|
log_write("cron", "info", "Deleting cron for socket server start.")
|
|
return ""
|
|
|
|
def set_cron_check_rain_sensor():
|
|
"""Imposta il crontab per il controllo della pioggia dal sensore."""
|
|
log_write("cron", "info", "Setting cron for rain sensor check.")
|
|
return ""
|
|
|
|
def del_cron_check_rain_sensor():
|
|
"""Rimuove il crontab per il controllo della pioggia dal sensore."""
|
|
log_write("cron", "info", "Deleting cron for rain sensor check.")
|
|
return ""
|
|
|
|
def set_cron_check_rain_online():
|
|
"""Imposta il crontab per il controllo della pioggia dal servizio online."""
|
|
log_write("cron", "info", "Setting cron for online rain check.")
|
|
return ""
|
|
|
|
def del_cron_check_rain_online():
|
|
"""Rimuove il crontab per il controllo della pioggia dal servizio online."""
|
|
log_write("cron", "info", "Deleting cron for online rain check.")
|
|
return ""
|
|
|
|
def set_cron_close_all_for_rain():
|
|
"""Imposta il crontab per chiudere tutte le elettrovalvole quando piove."""
|
|
log_write("cron", "info", "Setting cron for close all for rain.")
|
|
return ""
|
|
|
|
def del_cron_close_all_for_rain():
|
|
"""Rimuove il crontab per chiudere tutte le elettrovalvole quando piove."""
|
|
log_write("cron", "info", "Deleting cron for close all for rain.")
|
|
return ""
|
|
|
|
# --- FUNZIONI SENSORI (PLACEHOLDERS) ---
|
|
def json_sensor_status_all():
|
|
"""Restituisce lo stato di tutti i sensori in formato JSON."""
|
|
sensor_data = {}
|
|
for num, sensor_config in config.SENSOR_CONFIG.items():
|
|
alias = sensor_config["ALIAS"]
|
|
# Qui dovresti leggere lo stato reale del sensore
|
|
# Per ora, simulo uno stato casuale o fisso
|
|
status_value = 0 # Placeholder
|
|
sensor_data[alias] = {"alias": alias, "status": status_value}
|
|
return json.dumps(sensor_data)
|
|
|
|
def list_alias_sensor():
|
|
"""Stampa la lista degli alias dei sensori."""
|
|
for sensor_data in config.SENSOR_CONFIG.values():
|
|
print(sensor_data["ALIAS"])
|
|
|
|
def sensor_status(alias, sensor_type=None):
|
|
"""Mostra lo stato di un sensore."""
|
|
# Trova il sensore per alias
|
|
sensor_num = None
|
|
for num, s_config in config.SENSOR_CONFIG.items():
|
|
if s_config["ALIAS"] == alias:
|
|
sensor_num = num
|
|
break
|
|
|
|
if sensor_num is None:
|
|
log_write("sensor", "error", f"Sensor alias not found: {alias}")
|
|
message_write("warning", "Sensor alias not found")
|
|
json_status_wrapper()
|
|
return 1 # Errore
|
|
|
|
# Qui dovresti leggere il valore reale dal sensore
|
|
# Per ora, restituisco un valore fisso o casuale
|
|
value = 0 # Placeholder
|
|
print(value)
|
|
return value
|
|
|
|
def sensor_status_set(alias, sensor_type, value):
|
|
"""Imposta lo stato di un sensore (se supportato, es. per sensori virtuali)."""
|
|
log_write("sensor", "info", f"Setting sensor {alias} type {sensor_type} to value {value}")
|
|
# Implementa la logica per impostare lo stato del sensore
|
|
return 0 # Successo
|
|
|
|
def sensor_status_all():
|
|
"""Mostra lo stato di tutti i sensori."""
|
|
for num, sensor_config in config.SENSOR_CONFIG.items():
|
|
alias = sensor_config["ALIAS"]
|
|
# Qui dovresti leggere lo stato reale del sensore
|
|
status_value = 0 # Placeholder
|
|
print(f"{alias}: {status_value}")
|
|
|
|
|
|
# --- FUNZIONI PIOGGIA (PLACEHOLDERS) ---
|
|
def ev_check_moisture(ev_num):
|
|
"""Verifica l'umidità del terreno per un'elettrovalvola."""
|
|
log_write("irrigate", "info", f"Checking moisture for EV {ev_num}")
|
|
# Implementa la logica per leggere l'umidità del sensore associato all'EV
|
|
# Restituisce 0 se l'umidità è bassa (ok per irrigare), >0 altrimenti
|
|
return 0 # Placeholder: nessuna umidità alta
|
|
|
|
def check_rain_sensor():
|
|
"""Controlla la pioggia dal sensore hardware."""
|
|
if config.RAIN_GPIO:
|
|
log_write("rain", "info", f"Checking rain from sensor on GPIO {config.RAIN_GPIO}")
|
|
# Leggi lo stato del sensore GPIO
|
|
# Se piove, aggiorna il timestamp in config.STATUS_DIR/last_rain_sensor
|
|
# Esempio:
|
|
# if GPIO.input(config.RAIN_GPIO) == GPIO.LOW: # Assumendo LOW = pioggia
|
|
# with open(os.path.join(config.STATUS_DIR, "last_rain_sensor"), "w") as f:
|
|
# f.write(str(int(time.time())))
|
|
# log_write("rain", "info", "Rain detected by sensor.")
|
|
pass
|
|
else:
|
|
log_write("rain", "info", "Rain sensor not configured.")
|
|
|
|
def check_rain_online():
|
|
"""Controlla la pioggia da un servizio API online."""
|
|
if config.WEATHER_SERVICE and config.WEATHER_SERVICE != "none":
|
|
log_write("rain", "info", f"Checking rain from online service: {config.WEATHER_SERVICE}")
|
|
# Implementa la chiamata all'API meteo (es. con 'requests')
|
|
# Se piove, aggiorna il timestamp in config.STATUS_DIR/last_rain_online
|
|
# Esempio:
|
|
# import requests
|
|
# try:
|
|
# response = requests.get(f"https://api.example.com/weather?location={config.WEATHER_LOCATION}&api_key={config.WEATHER_API_KEY}")
|
|
# data = response.json()
|
|
# if data.get("rain_detected"): # Logica specifica per il tuo servizio
|
|
# with open(os.path.join(config.STATUS_DIR, "last_rain_online"), "w") as f:
|
|
# f.write(str(int(time.time())))
|
|
# log_write("rain", "info", "Rain detected by online service.")
|
|
# except Exception as e:
|
|
# log_write("rain", "error", f"Error checking online rain: {e}")
|
|
pass
|
|
else:
|
|
log_write("rain", "info", "Online rain service not configured.")
|
|
|
|
def reset_last_rain_sensor_timestamp():
|
|
"""Resetta il timestamp dell'ultima pioggia dal sensore."""
|
|
file_path = os.path.join(config.STATUS_DIR, "last_rain_sensor")
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
log_write("rain", "info", "Timestamp of last sensor rain successfully reset.")
|
|
|
|
def reset_last_rain_online_timestamp():
|
|
"""Resetta il timestamp dell'ultima pioggia online."""
|
|
file_path = os.path.join(config.STATUS_DIR, "last_rain_online")
|
|
if os.path.exists(file_path):
|
|
os.remove(file_path)
|
|
log_write("rain", "info", "Timestamp of last online rain successfully reset.")
|
|
|
|
def last_rain_sensor_timestamp():
|
|
"""Mostra il timestamp dell'ultima pioggia dal sensore."""
|
|
file_path = os.path.join(config.STATUS_DIR, "last_rain_sensor")
|
|
if os.path.exists(file_path):
|
|
with open(file_path, "r") as f:
|
|
print(f.read().strip())
|
|
else:
|
|
print("")
|
|
|
|
def last_rain_online_timestamp():
|
|
"""Mostra il timestamp dell'ultima pioggia online."""
|
|
file_path = os.path.join(config.STATUS_DIR, "last_rain_online")
|
|
if os.path.exists(file_path):
|
|
with open(file_path, "r") as f:
|
|
print(f.read().strip())
|
|
else:
|
|
print("")
|
|
|
|
# --- FUNZIONI EVENTI (PLACEHOLDERS) ---
|
|
# Queste funzioni simulano il sistema di eventi.
|
|
# Potresti voler implementare un sistema di eventi più robusto (es. con un dispatcher).
|
|
def trigger_event(event_name, alias="", param1="", param2=""):
|
|
"""Triggera un evento."""
|
|
config.CURRENT_EVENT = event_name
|
|
config.CURRENT_EVENT_ALIAS = alias
|
|
log_write("event", "info", f"Triggering event: {event_name} (alias: {alias}, params: {param1}, {param2})")
|
|
# Qui potresti chiamare script esterni o funzioni Python basate sull'evento
|
|
# Esempio:
|
|
# if event_name == "init_before":
|
|
# # Esegui codice specifico per init_before
|
|
# pass
|
|
return 0 # 0 per successo, diverso da 0 per errore (come in Bash)
|
|
|
|
# --- FUNZIONI PRINCIPALI DEL SISTEMA ---
|
|
|
|
def initialize():
|
|
"""Inizializza le elettrovalvole e l'alimentazione."""
|
|
log_write("general", "info", "Run initialize")
|
|
unlock() # Assicurati che non ci siano lock pendenti
|
|
|
|
trigger_event("init_before", "")
|
|
|
|
# Inizializza i driver gpio
|
|
# list_drv non è definito nello script Bash fornito, presumo sia una lista di driver
|
|
# Per ora, useremo solo drv_init
|
|
log_write("drv", "info", "Initializing all drivers...")
|
|
drv_init() # Chiamata generica per inizializzare tutti i driver
|
|
|
|
# Imposta l'alimentazione con voltaggio negativo e setta i gpio in scrittura per le elettrovalvole bistabili
|
|
if config.EV_MONOSTABLE != 1:
|
|
# SUPPLY_GPIO_1 e SUPPLY_GPIO_2 non sono definiti in config, dovresti aggiungerli
|
|
# Esempio: config.SUPPLY_GPIO_1 = 5, config.SUPPLY_GPIO_2 = 6
|
|
drv_supply_bistable_init(getattr(config, 'SUPPLY_GPIO_1', None), getattr(config, 'SUPPLY_GPIO_2', None))
|
|
|
|
# Elimina tutti gli stati delle elettrovalvole preesistenti
|
|
try:
|
|
for f in os.listdir(config.STATUS_DIR):
|
|
if f.startswith("ev"):
|
|
os.remove(os.path.join(config.STATUS_DIR, f))
|
|
log_write("general", "info", "Removed existing EV status files.")
|
|
except Exception as e:
|
|
log_write("general", "error", f"Error removing EV status files: {e}")
|
|
|
|
# Inizializza i gpio delle elettrovalvole e ne chiude l'alimentazione
|
|
for i in range(1, config.EV_TOTAL + 1):
|
|
gpio = ev_number2gpio(i)
|
|
if gpio is not None:
|
|
drv_rele_init(gpio)
|
|
ev_set_state(i, 0) # Imposta lo stato iniziale a chiuso
|
|
|
|
# Chiude tutte le elettrovalvole
|
|
for i in range(1, config.EV_TOTAL + 1):
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS")
|
|
if alias:
|
|
ev_close(alias) # Chiama ev_close per ogni valvola
|
|
|
|
# Inizializza il sensore di rilevamento pioggia
|
|
if config.RAIN_GPIO:
|
|
drv_rain_sensor_init(config.RAIN_GPIO)
|
|
log_write("rain", "info", "Rain sensor initialized")
|
|
else:
|
|
log_write("rain", "info", "Rain sensor not present")
|
|
|
|
trigger_event("init_after", "")
|
|
log_write("general", "info", "End initialize")
|
|
|
|
def ev_open(alias, force=""):
|
|
"""Commuta un'elettrovalvola nello stato aperto."""
|
|
cron_del("open_in", alias) # Non catturiamo l'output, come &> /dev/null
|
|
|
|
ev_num = ev_alias2number(alias)
|
|
gpio = ev_number2gpio(ev_num)
|
|
ev_norain = ev_number2norain(ev_num)
|
|
ev_is_remote = ev_number2remote(ev_num)
|
|
ev_is_monostable = ev_number2monostable(ev_num)
|
|
|
|
if force != "force":
|
|
moisture = ev_check_moisture(ev_num)
|
|
if moisture > 0:
|
|
message_write("warning", "solenoid not open because maximum soil moisture has been reached")
|
|
trigger_event("ev_not_open_for_moisture", alias)
|
|
log_write("irrigate", "warning", f"Solenoid '{alias}' not open because maximum soil moisture has been reached")
|
|
return
|
|
|
|
if ev_norain != 1:
|
|
now = int(time.time())
|
|
|
|
# Controllo pioggia online
|
|
last_rain_online_file = os.path.join(config.STATUS_DIR, "last_rain_online")
|
|
if config.NOT_IRRIGATE_IF_RAIN_ONLINE > 0 and os.path.exists(last_rain_online_file):
|
|
try:
|
|
with open(last_rain_online_file, "r") as f:
|
|
last_rain_online = int(f.read().strip())
|
|
diff = now - last_rain_online
|
|
if diff < config.NOT_IRRIGATE_IF_RAIN_ONLINE and moisture == 0: # Bash aveva moisture -ne 0
|
|
message_write("warning", "Solenoid not open for rain")
|
|
trigger_event("ev_not_open_for_rain_online", alias)
|
|
trigger_event("ev_not_open_for_rain", alias)
|
|
log_write("irrigate", "warning", f"Solenoid '{alias}' not open for rain (online check)")
|
|
return
|
|
except (IOError, ValueError):
|
|
pass # Ignora errori di lettura del file
|
|
|
|
# Controllo pioggia sensore
|
|
check_rain_sensor() # Assicurati che aggiorni last_rain_sensor
|
|
last_rain_sensor_file = os.path.join(config.STATUS_DIR, "last_rain_sensor")
|
|
if config.NOT_IRRIGATE_IF_RAIN_SENSOR > 0 and os.path.exists(last_rain_sensor_file):
|
|
try:
|
|
with open(last_rain_sensor_file, "r") as f:
|
|
last_rain_sensor = int(f.read().strip())
|
|
diff = now - last_rain_sensor
|
|
if diff < config.NOT_IRRIGATE_IF_RAIN_SENSOR and moisture == 0: # Bash aveva moisture -ne 0
|
|
message_write("warning", "Solenoid not open for rain")
|
|
trigger_event("ev_not_open_for_rain_sensor", alias)
|
|
trigger_event("ev_not_open_for_rain", alias)
|
|
log_write("irrigate", "warning", f"Solenoid '{alias}' not open for rain (sensor check)")
|
|
return
|
|
except (IOError, ValueError):
|
|
pass # Ignora errori di lettura del file
|
|
|
|
state = 1
|
|
if force == "force":
|
|
state = 2
|
|
|
|
if trigger_event("ev_open_before", alias, force) != 0:
|
|
log_write("irrigate", "warning", f"Solenoid '{alias}' not open due to external event")
|
|
message_write('warning', "Solenoid not open due to external event")
|
|
mqtt_status() # Chiamata a mqtt_status (placeholder)
|
|
return
|
|
|
|
lock()
|
|
|
|
# Gestisce l'apertura dell'elettrovalvola in base alla tipologia (monostabile / bistabile)
|
|
if config.EV_MONOSTABLE == 1 or ev_is_remote == 1 or ev_is_monostable == 1:
|
|
if drv_rele_close(gpio) == 1: # Se drv_rele_close restituisce 1 per errore
|
|
unlock()
|
|
return
|
|
else:
|
|
supply_positive() # supply_positive(config.SUPPLY_GPIO_1, config.SUPPLY_GPIO_2)
|
|
drv_rele_close(gpio)
|
|
time.sleep(1)
|
|
drv_rele_open(gpio)
|
|
|
|
ev_set_state(ev_num, state)
|
|
|
|
log_write("irrigate", "info", f"Solenoid '{alias}' open")
|
|
message_write("success", "Solenoid open")
|
|
|
|
trigger_event("ev_open_after", alias, force)
|
|
|
|
unlock()
|
|
|
|
def ev_open_in(minute_start_str, minute_stop_str, alias, force=""):
|
|
"""Commuta un'elettrovalvola nello stato aperto dopo un intervallo."""
|
|
try:
|
|
minute_start = int(minute_start_str)
|
|
minute_stop = int(minute_stop_str)
|
|
except ValueError:
|
|
print("Time start/stop of irrigation is wrong or not specified", file=sys.stderr)
|
|
message_write("warning", "Time start/stop of irrigation is wrong or not specified")
|
|
mqtt_status()
|
|
return 1
|
|
|
|
if minute_stop < 1:
|
|
print("Time stop of irrigation is wrong", file=sys.stderr)
|
|
message_write("warning", "Time stop of irrigation is wrong")
|
|
mqtt_status()
|
|
return 1
|
|
|
|
if not alias:
|
|
print("Alias solenoid not specified", file=sys.stderr)
|
|
message_write("warning", "Alias solenoid not specified")
|
|
mqtt_status()
|
|
return 1
|
|
|
|
trigger_event("ev_open_in_before", alias, force, minute_start_str, minute_stop_str)
|
|
|
|
# gpio_alias2number $alias > /dev/null 2>&1
|
|
# Questa riga in Bash sembra solo verificare l'esistenza dell'alias e uscire in caso di errore.
|
|
# La funzione ev_alias2number già fa questo.
|
|
ev_alias2number(alias) # Per verificare che l'alias esista
|
|
|
|
minute_start_actual = minute_start + 1
|
|
minute_stop_actual = minute_start_actual + minute_stop
|
|
|
|
# Calcola le date/ore per cron
|
|
# In Python, non generiamo stringhe cron direttamente per `date -d`,
|
|
# ma calcoliamo i timestamp e li passiamo alle funzioni cron_add/del.
|
|
# Le funzioni cron_add/del dovranno poi convertire questi timestamp in stringhe cron se necessario.
|
|
|
|
# Per semplicità, qui useremo un formato simile a quello di Bash per cron_start/stop
|
|
# che le funzioni cron_add/del dovranno interpretare.
|
|
# cron_start_dt = datetime.datetime.now() + datetime.timedelta(minutes=minute_start_actual)
|
|
# cron_stop_dt = datetime.datetime.now() + datetime.timedelta(minutes=minute_stop_actual)
|
|
# cron_start_str_for_cron = cron_start_dt.strftime("%M %H %d %m %w") # %w per giorno della settimana (0-6)
|
|
# cron_stop_str_for_cron = cron_stop_dt.strftime("%M %H %d %m %w")
|
|
|
|
# Replicando il formato Bash per cron_add/del
|
|
# Il formato è "MM HH DD MM DW" (minuto, ora, giorno del mese, mese, giorno della settimana)
|
|
# dove DW è 0-6 (domenica-sabato) per `date +%u` in Bash.
|
|
# Python's datetime.strftime('%w') restituisce 0-6 (domenica-sabato)
|
|
|
|
# Questo è un placeholder. Le funzioni cron_add/del dovrebbero gestire la logica cron reale.
|
|
# Qui passiamo solo i minuti relativi.
|
|
cron_del("open_in", alias)
|
|
cron_del("open_in_stop", alias)
|
|
|
|
if minute_start_actual == 1:
|
|
ev_open(alias, force)
|
|
cron_start_info = "- - - - -" # Come in Bash
|
|
else:
|
|
# In un sistema reale, cron_add riceverebbe la data/ora esatta o i minuti di ritardo
|
|
# e si occuperebbe di schedulare il job.
|
|
cron_add("open_in", f"+{minute_start_actual}m", alias, force)
|
|
cron_start_info = f"Scheduled in {minute_start_actual} minutes" # Info per il log
|
|
|
|
cron_add("open_in_stop", f"+{minute_stop_actual}m", alias)
|
|
cron_stop_info = f"Scheduled stop in {minute_stop_actual} minutes" # Info per il log
|
|
|
|
message_write("success", "Scheduled start successfully performed")
|
|
|
|
trigger_event("ev_open_in_after", alias, force, cron_start_info, cron_stop_info)
|
|
return 0
|
|
|
|
|
|
def ev_close(alias):
|
|
"""Commuta un'elettrovalvola nello stato chiuso."""
|
|
ev_num = ev_alias2number(alias)
|
|
gpio = ev_number2gpio(ev_num)
|
|
ev_is_remote = ev_number2remote(ev_num)
|
|
|
|
trigger_event("ev_close_before", alias)
|
|
|
|
lock()
|
|
|
|
if config.EV_MONOSTABLE == 1 or ev_is_remote == 1:
|
|
if drv_rele_open(gpio) == 1: # Se drv_rele_open restituisce 1 per errore
|
|
unlock()
|
|
return
|
|
else:
|
|
supply_negative() # supply_negative(config.SUPPLY_GPIO_1, config.SUPPLY_GPIO_2)
|
|
drv_rele_close(gpio)
|
|
time.sleep(1)
|
|
drv_rele_open(gpio)
|
|
|
|
ev_set_state(ev_num, 0) # 0 = chiuso
|
|
|
|
log_write("irrigate", "info", f"Solenoid '{alias}' close")
|
|
message_write("success", "Solenoid close")
|
|
|
|
trigger_event("ev_close_after", alias)
|
|
|
|
unlock()
|
|
|
|
cron_del("open_in_stop", alias) # Non catturiamo l'output, come &> /dev/null
|
|
|
|
def supply_positive():
|
|
"""Imposta l'alimentazione delle elettrovalvole con voltaggio positivo."""
|
|
# SUPPLY_GPIO_1 e SUPPLY_GPIO_2 non sono definiti in config, dovresti aggiungerli
|
|
drv_supply_positive(getattr(config, 'SUPPLY_GPIO_1', None), getattr(config, 'SUPPLY_GPIO_2', None))
|
|
|
|
def supply_negative():
|
|
"""Imposta l'alimentazione delle elettrovalvole con voltaggio negativo."""
|
|
# SUPPLY_GPIO_1 e SUPPLY_GPIO_2 non sono definiti in config, dovresti aggiungerli
|
|
drv_supply_negative(getattr(config, 'SUPPLY_GPIO_1', None), getattr(config, 'SUPPLY_GPIO_2', None))
|
|
|
|
def send_identifier():
|
|
"""Invia l'identificativo univoco ad uso statistico di utilizzo."""
|
|
if config.NO_SEND_IDENTIFIER == 1:
|
|
return
|
|
|
|
file_id_path = "/tmp/pigarden.id"
|
|
|
|
if os.path.exists(file_id_path):
|
|
max_age_file = 86400 # 1 giorno in secondi
|
|
try:
|
|
time_file = os.path.getmtime(file_id_path) # Tempo dell'ultima modifica
|
|
age_file = int(time.time()) - int(time_file)
|
|
if age_file < max_age_file:
|
|
return # ID troppo giovane, non inviare
|
|
except OSError as e:
|
|
log_write("send_identifier", "error", f"Error getting file modification time for {file_id_path}: {e}")
|
|
return
|
|
|
|
# Genera l'ID (come nello script Bash precedente)
|
|
try:
|
|
ifconfig_output = subprocess.check_output(['/sbin/ifconfig'], encoding='utf-8')
|
|
mac_address_pattern = re.compile(r'([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2}')
|
|
match = mac_address_pattern.search(ifconfig_output)
|
|
|
|
if match:
|
|
mac_address = match.group(0)
|
|
id_hash = hashlib.md5(mac_address.encode('utf-8')).hexdigest()
|
|
else:
|
|
log_write("send_identifier", "warning", "No MAC address found for identifier.")
|
|
return
|
|
except (FileNotFoundError, subprocess.CalledProcessError) as e:
|
|
log_write("send_identifier", "error", f"Error getting MAC address for identifier: {e}")
|
|
return
|
|
|
|
if not id_hash:
|
|
return
|
|
|
|
try:
|
|
with open(file_id_path, "w") as f:
|
|
f.write(id_hash)
|
|
except IOError as e:
|
|
log_write("send_identifier", "error", f"Error writing identifier to {file_id_path}: {e}")
|
|
return
|
|
|
|
log_write("general", "info", "Send installation identifier to collect usage")
|
|
|
|
# Invio via CURL (subprocess.Popen per emulare nohup ... &)
|
|
url = f"https://www.lejubila.net/statistic/collect_usage/piGarden/{id_hash}/{config.VERSION}/{config.SUB_VERSION}/{config.RELEASE_VERSION}"
|
|
cmd = ["curl", url]
|
|
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
def exec_poweroff():
|
|
"""Spegne il sistema."""
|
|
trigger_event("exec_poweroff_before", "")
|
|
log_write("system", "warning", "Executing system poweroff...")
|
|
# In un sistema reale, questo script potrebbe chiamare un altro script Python o un comando di sistema.
|
|
# Per replicare il comportamento Bash, potremmo chiamare un comando di sistema.
|
|
# Il sleep 15 è strano, forse per dare tempo al log di essere scritto.
|
|
time.sleep(15) # Simula il sleep
|
|
|
|
# Esegui lo script poweroff.sh se esiste, altrimenti il comando di sistema
|
|
poweroff_script_path = os.path.join(config.DIR_SCRIPT, "scripts", "poweroff.sh")
|
|
if os.path.exists(poweroff_script_path) and os.access(poweroff_script_path, os.X_OK):
|
|
try:
|
|
subprocess.run([poweroff_script_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except subprocess.CalledProcessError as e:
|
|
log_write("system", "error", f"Error executing poweroff script: {e}")
|
|
else:
|
|
try:
|
|
# Assicurati che l'utente abbia i permessi sudo senza password per 'poweroff'
|
|
subprocess.run(["sudo", "poweroff"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except subprocess.CalledProcessError as e:
|
|
log_write("system", "error", f"Error executing sudo poweroff: {e}")
|
|
except FileNotFoundError:
|
|
log_write("system", "error", "Comando 'sudo' o 'poweroff' non trovato.")
|
|
|
|
trigger_event("exec_poweroff_after", "")
|
|
|
|
def exec_reboot():
|
|
"""Riavvia il sistema."""
|
|
trigger_event("exec_reboot_before", "")
|
|
log_write("system", "warning", "Executing system reboot...")
|
|
time.sleep(15) # Simula il sleep
|
|
|
|
reboot_script_path = os.path.join(config.DIR_SCRIPT, "scripts", "reboot.sh")
|
|
if os.path.exists(reboot_script_path) and os.access(reboot_script_path, os.X_OK):
|
|
try:
|
|
subprocess.run([reboot_script_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except subprocess.CalledProcessError as e:
|
|
log_write("system", "error", f"Error executing reboot script: {e}")
|
|
else:
|
|
try:
|
|
# Assicurati che l'utente abbia i permessi sudo senza password per 'reboot'
|
|
subprocess.run(["sudo", "reboot"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
except subprocess.CalledProcessError as e:
|
|
log_write("system", "error", f"Error executing sudo reboot: {e}")
|
|
except FileNotFoundError:
|
|
log_write("system", "error", "Comando 'sudo' o 'reboot' non trovato.")
|
|
|
|
trigger_event("exec_reboot_after", "")
|
|
|
|
def cmd_pigardensched(*args):
|
|
"""Esegue un comando con piGardenSched."""
|
|
if config.PIGARDENSCHED == 0:
|
|
print("piGardenSched not configured in piGarden", file=sys.stderr)
|
|
log_write("piGardenSched", "error", "piGardenSched not configured in piGarden")
|
|
return 1 # Errore
|
|
|
|
try:
|
|
# Passa tutti gli argomenti al sottoprocesso
|
|
result = subprocess.run([config.PIGARDENSCHED_PATH] + list(args), capture_output=True, text=True, check=True)
|
|
# print(result.stdout.strip()) # Stampa l'output di piGardenSched se necessario
|
|
return 0 # Successo
|
|
except subprocess.CalledProcessError as e:
|
|
print("piGardenSched command failed", file=sys.stderr)
|
|
log_write("piGardenSched", "error", f"piGardenSched command failed: {e.stderr.strip()}")
|
|
return 1 # Errore
|
|
except FileNotFoundError:
|
|
print(f"piGardenSched executable not found at {config.PIGARDENSCHED_PATH}", file=sys.stderr)
|
|
log_write("piGardenSched", "error", f"piGardenSched executable not found at {config.PIGARDENSCHED_PATH}")
|
|
return 1
|
|
|
|
def deg2dir(degrees):
|
|
"""Converte da gradi a direzione."""
|
|
try:
|
|
deg = int(float(degrees)) # Gestisce anche i decimali come in Bash (sed 's/\..*$//')
|
|
except (ValueError, TypeError):
|
|
return "" # Come in Bash per "null" o input non valido
|
|
|
|
if deg <= 11: return "North"
|
|
elif deg <= 33: return "NNE"
|
|
elif deg <= 56: return "NE"
|
|
elif deg <= 78: return "ENE"
|
|
elif deg <= 101: return "East"
|
|
elif deg <= 123: return "ESE"
|
|
elif deg <= 146: return "SE"
|
|
elif deg <= 168: return "SSE"
|
|
elif deg <= 191: return "South"
|
|
elif deg <= 213: return "SSW"
|
|
elif deg <= 236: return "SW"
|
|
elif deg <= 258: return "WSW"
|
|
elif deg <= 281: return "West"
|
|
elif deg <= 303: return "WNW"
|
|
elif deg <= 326: return "NW"
|
|
elif deg <= 348: return "NNW"
|
|
else: return "North"
|
|
|
|
def debug1(*args):
|
|
"""Esegue il codice di debug 1."""
|
|
log_write("debug", "info", f"Running debug1 with args: {args}")
|
|
# Qui potresti importare ed eseguire un modulo Python dedicato al debug
|
|
# Esempio:
|
|
# from debug import debug1_script
|
|
# debug1_script.run(*args)
|
|
print("Debug1 executed (placeholder).")
|
|
|
|
def debug2(*args):
|
|
"""Esegue il codice di debug 2."""
|
|
log_write("debug", "info", f"Running debug2 with args: {args}")
|
|
# Esempio:
|
|
# from debug import debug2_script
|
|
# debug2_script.run(*args)
|
|
print("Debug2 executed (placeholder).")
|
|
|
|
# --- FUNZIONE JSON STATUS ---
|
|
# Questa è una funzione complessa che richiede molte dipendenze.
|
|
# Ho cercato di replicare la sua logica il più fedelmente possibile.
|
|
def json_status_wrapper(*args_from_cli):
|
|
"""
|
|
Stampa un json contenente lo status della centralina.
|
|
I parametri opzionali controllano quali dati aggiuntivi includere.
|
|
"""
|
|
json_data = {}
|
|
|
|
# Inizializza i messaggi (come le variabili globali in Bash)
|
|
json_data["info"] = config.MESSAGE_INFO
|
|
json_data["warning"] = config.MESSAGE_WARNING
|
|
json_data["success"] = config.MESSAGE_SUCCESS
|
|
|
|
# Versione
|
|
json_data["version"] = {
|
|
"ver": config.VERSION,
|
|
"sub": config.SUB_VERSION,
|
|
"rel": config.RELEASE_VERSION
|
|
}
|
|
|
|
# Timestamp
|
|
json_data["timestamp"] = int(time.time())
|
|
|
|
# Evento corrente
|
|
json_data["event"] = {
|
|
"event": config.CURRENT_EVENT,
|
|
"alias": config.CURRENT_EVENT_ALIAS
|
|
}
|
|
|
|
# PID corrente (PARENT_PID se specificato)
|
|
current_pid = os.getpid()
|
|
if config.PARENT_PID > 0:
|
|
current_pid = config.PARENT_PID
|
|
json_data["pid"] = current_pid # Aggiunto per chiarezza, non esplicito in Bash ma utile
|
|
|
|
# Parsing degli argomenti per includere dati aggiuntivi
|
|
with_get_cron = "0"
|
|
with_get_cron_open_in = "0"
|
|
with_get_schedule = "0"
|
|
|
|
for arg in args_from_cli:
|
|
if arg == "get_cron":
|
|
with_get_cron = "1"
|
|
elif arg.startswith("get_cron:"):
|
|
with_get_cron = arg.split(":")[1]
|
|
elif arg == "get_cron_open_in":
|
|
with_get_cron_open_in = "1"
|
|
elif arg.startswith("get_cron_open_in:"):
|
|
with_get_cron_open_in = arg.split(":")[1]
|
|
elif arg == "get_schedule" and config.PIGARDENSCHED == 1:
|
|
with_get_schedule = "1"
|
|
|
|
# Stato delle zone/elettrovalvole
|
|
zones_data = {}
|
|
ev_data_aliases = {}
|
|
for i in range(1, config.EV_TOTAL + 1):
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS", f"EV{i}")
|
|
state = ev_get_state(i) # ev_status $av > /dev/null; local sv=$?
|
|
zones_data[alias] = {"name": alias, "state": state}
|
|
ev_data_aliases[f"EV{i}"] = {"alias": alias} # EVx_ALIAS
|
|
json_data["zones"] = zones_data
|
|
json_data["ev"] = ev_data_aliases
|
|
|
|
# Ultima pioggia (sensore e online)
|
|
last_rain_sensor_file = os.path.join(config.STATUS_DIR, "last_rain_sensor")
|
|
last_rain_online_file = os.path.join(config.STATUS_DIR, "last_rain_online")
|
|
last_weather_online_file = os.path.join(config.STATUS_DIR, "last_weather_online")
|
|
|
|
json_data["last_rain_sensor"] = ""
|
|
if os.path.exists(last_rain_sensor_file):
|
|
try:
|
|
with open(last_rain_sensor_file, "r") as f:
|
|
json_data["last_rain_sensor"] = f.read().strip()
|
|
except IOError:
|
|
pass
|
|
|
|
json_data["last_rain_online"] = ""
|
|
if os.path.exists(last_rain_online_file):
|
|
try:
|
|
with open(last_rain_online_file, "r") as f:
|
|
json_data["last_rain_online"] = f.read().strip()
|
|
except IOError:
|
|
pass
|
|
|
|
json_data["last_weather_online"] = "" # In Bash era un JSON string, qui lo mettiamo come stringa vuota o il contenuto del file
|
|
if os.path.exists(last_weather_online_file):
|
|
try:
|
|
with open(last_weather_online_file, "r") as f:
|
|
content = f.read().strip()
|
|
# Se il contenuto è già un JSON valido, lo si può includere direttamente
|
|
# Altrimenti, lo si lascia come stringa
|
|
try:
|
|
json.loads(content) # Prova a parsare come JSON
|
|
json_data["last_weather_online"] = json.loads(content)
|
|
except json.JSONDecodeError:
|
|
json_data["last_weather_online"] = content # Lascia come stringa se non è JSON valido
|
|
except IOError:
|
|
pass
|
|
|
|
# Errore (sempre presente nel JSON Bash)
|
|
json_data["error"] = {"code": 0, "description": ""} # Placeholder, dovrebbe essere aggiornato dalle funzioni di errore
|
|
|
|
# Dati cron (open/close)
|
|
cron_open_data = {}
|
|
cron_close_data = {}
|
|
if with_get_cron != "0":
|
|
element_for_cron = []
|
|
if with_get_cron == "1":
|
|
element_for_cron = range(1, config.EV_TOTAL + 1)
|
|
else:
|
|
try:
|
|
element_for_cron = [ev_alias2number(with_get_cron)]
|
|
except SystemExit: # ev_alias2number esce in caso di errore
|
|
pass # Non aggiungere cron data se l'alias non esiste
|
|
|
|
for i in element_for_cron:
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS")
|
|
if alias:
|
|
crn_open = cron_get("open", alias).replace("\n", "%%")
|
|
cron_open_data[alias] = crn_open
|
|
crn_close = cron_get("close", alias).replace("\n", "%%")
|
|
cron_close_data[alias] = crn_close
|
|
json_data["cron"] = {"open": cron_open_data, "close": cron_close_data}
|
|
|
|
# Dati schedule (piGardenSched)
|
|
schedule_data = {}
|
|
if with_get_schedule == "1":
|
|
try:
|
|
# Esegui piGardenSched e parsa l'output
|
|
result = subprocess.run([config.PIGARDENSCHED_PATH, "sched"], capture_output=True, text=True, check=True)
|
|
for line in result.stdout.strip().split('\n'):
|
|
if line:
|
|
parts = line.split(';')
|
|
if len(parts) >= 1:
|
|
ev_key = parts[0]
|
|
# Cerca l'alias corrispondente
|
|
alias_found = ""
|
|
for num, ev_conf in config.EV_CONFIG.items():
|
|
if f"EV{num}" == ev_key:
|
|
alias_found = ev_conf["ALIAS"]
|
|
break
|
|
if alias_found:
|
|
schedule_data[ev_key] = {"alias": alias_found, "entry": line}
|
|
else:
|
|
log_write("json_status", "warning", f"Alias not found for scheduled EV key: {ev_key}")
|
|
json_data["schedule"] = schedule_data
|
|
except (subprocess.CalledProcessError, FileNotFoundError) as e:
|
|
log_write("json_status", "error", f"Error getting piGardenSched schedule: {e}")
|
|
json_data["schedule"] = {} # In caso di errore, restituisci un oggetto vuoto
|
|
else:
|
|
json_data["schedule"] = {} # Sempre includi la chiave, anche se vuota
|
|
|
|
# Dati cron_open_in
|
|
cron_open_in_data = {}
|
|
cron_open_in_stop_data = {}
|
|
if with_get_cron_open_in != "0":
|
|
element_for_cron_open_in = []
|
|
if with_get_cron_open_in == "1":
|
|
element_for_cron_open_in = range(1, config.EV_TOTAL + 1)
|
|
else:
|
|
try:
|
|
element_for_cron_open_in = [ev_alias2number(with_get_cron_open_in)]
|
|
except SystemExit:
|
|
pass
|
|
|
|
for i in element_for_cron_open_in:
|
|
alias = config.EV_CONFIG.get(i, {}).get("ALIAS")
|
|
if alias:
|
|
crn_open_in = cron_get("open_in", alias).replace("\n", "%%")
|
|
cron_open_in_data[alias] = crn_open_in
|
|
crn_open_in_stop = cron_get("open_in_stop", alias).replace("\n", "%%")
|
|
cron_open_in_stop_data[alias] = crn_open_in_stop
|
|
json_data["cron_open_in"] = {"open_in": cron_open_in_data, "open_in_stop": cron_open_in_stop_data}
|
|
|
|
# Stato dei sensori
|
|
json_data["sensors"] = json.loads(json_sensor_status_all()) # Assumendo che json_sensor_status_all restituisca JSON string
|
|
|
|
# Stampa il JSON finale
|
|
print(json.dumps(json_data, indent=2)) # indent=2 per una stampa leggibile
|
|
|
|
# --- MQTT STATUS ---
|
|
def mqtt_status(parent_pid=None):
|
|
"""Invia al broker mqtt il json contenente lo stato del sistema."""
|
|
if not config.MQTT_ENABLE == 1:
|
|
return
|
|
|
|
if parent_pid is not None:
|
|
config.PARENT_PID = parent_pid
|
|
|
|
# Genera il JSON di stato
|
|
# Cattura l'output di json_status_wrapper() reindirizzando stdout
|
|
old_stdout = sys.stdout
|
|
sys.stdout = captured_output = io.StringIO()
|
|
json_status_wrapper()
|
|
sys.stdout = old_stdout
|
|
js = captured_output.getvalue().strip()
|
|
|
|
try:
|
|
# Usa paho-mqtt per inviare il messaggio
|
|
# pip install paho-mqtt
|
|
import paho.mqtt.client as mqtt
|
|
|
|
client = mqtt.Client(client_id=config.MQTT_CLIENT_ID)
|
|
if config.MQTT_USER and config.MQTT_PWD:
|
|
client.username_pw_set(config.MQTT_USER, config.MQTT_PWD)
|
|
|
|
client.connect(config.MQTT_HOST, config.MQTT_PORT, 60) # 60 secondi di keepalive
|
|
client.publish(config.MQTT_TOPIC, js, qos=1, retain=True) # retain=True come -r
|
|
client.disconnect()
|
|
log_write("mqtt", "info", "MQTT status published successfully.")
|
|
except ImportError:
|
|
log_write("mqtt", "error", "paho-mqtt module not found. Cannot publish MQTT status.")
|
|
except Exception as e:
|
|
log_write("mqtt", "error", f"Error publishing MQTT status: {e}")
|
|
|
|
|
|
# --- FUNZIONE PRINCIPALE (CLI DISPATCHER) ---
|
|
|
|
def show_usage():
|
|
"""Mostra i parametri dello script."""
|
|
print(f"piGarden v. {config.VERSION}.{config.SUB_VERSION}.{config.RELEASE_VERSION}")
|
|
print("\nUsage:")
|
|
print(f"\t{config.NAME_SCRIPT} init initialize supply and solenoid in closed state")
|
|
print(f"\t{config.NAME_SCRIPT} open alias [force] open a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} open_in minute_start minute_stop alias [force] open a solenoid in minute_start for minute_stop")
|
|
print(f"\t{config.NAME_SCRIPT} close alias close a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} list_alias view list of aliases solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} ev_status alias show status solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} ev_status_all show status solenoids")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} list_alias_sensor view list of aliases sensor")
|
|
print(f"\t{config.NAME_SCRIPT} sensor_status alias [type] show status sensor (type: {config.SENSOR_STATE_TYPE})")
|
|
print(f"\t{config.NAME_SCRIPT} sensor_status_set alias type value set status of sensor (type: {config.SENSOR_STATE_TYPE})")
|
|
print(f"\t{config.NAME_SCRIPT} sensor_status_all show status of all sensors")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} last_rain_sensor_timestamp show timestamp of last rain sensor")
|
|
print(f"\t{config.NAME_SCRIPT} last_rain_online_timestamp show timestamp of last rain online")
|
|
print(f"\t{config.NAME_SCRIPT} reset_last_rain_sensor_timestamp show timestamp of last rain sensor")
|
|
print(f"\t{config.NAME_SCRIPT} reset_last_rain_online_timestamp show timestamp of last rain online")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} json_status [get_cron|get_cron_open_in|get_schedule] show status in json format")
|
|
print(f"\t{config.NAME_SCRIPT} mqtt_status send status in json format to mqtt broker")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} check_rain_online check rain from online api service")
|
|
print(f"\t{config.NAME_SCRIPT} check_rain_sensor check rain from hardware sensor")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} close_all_for_rain close all solenoid if it's raining")
|
|
print(f"\t{config.NAME_SCRIPT} close_all [force] close all solenoid")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} start_socket_server [force] start socket server, with 'force' parameter force close socket server if already open")
|
|
print(f"\t{config.NAME_SCRIPT} stop_socket_server stop socket server")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} reboot reboot system")
|
|
print(f"\t{config.NAME_SCRIPT} poweroff shutdown system")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} set_cron_init set crontab for initialize control unit")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_init remove crontab for initialize control unit")
|
|
print(f"\t{config.NAME_SCRIPT} set_cron_start_socket_server set crontab for start socket server")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_start_socket_server remove crontab for start socket server")
|
|
print(f"\t{config.NAME_SCRIPT} set_cron_check_rain_sensor set crontab for check rein from sensor")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_check_rain_sensor remove crontab for check rein from sensor")
|
|
print(f"\t{config.NAME_SCRIPT} set_cron_check_rain_online set crontab for check rein from online service")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_check_rain_online remove crontab for check rein from online service")
|
|
print(f"\t{config.NAME_SCRIPT} set_cron_close_all_for_rain set crontab for close all solenoid when raining")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_close_all_for_rain remove crontab for close all solenoid when raining")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} add_cron_open alias m h dom mon dow [disabled] add crontab for open a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_open alias remove all crontab for open a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} get_cron_open alias get all crontab for open a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_open_in alias remove all crontab for open_in a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} add_cron_close alias m h dom mon dow [disabled] add crontab for close a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} del_cron_close alias remove all crontab for close a solenoid")
|
|
print(f"\t{config.NAME_SCRIPT} get_cron_close alias get all crontab for close a solenoid")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} cmd_pigardensched [prm1 [prm2 [prm3]...]] performs a pigardensched command")
|
|
print("\n")
|
|
print(f"\t{config.NAME_SCRIPT} debug1 [parameter]|[parameter]|..] Run debug code 1")
|
|
print(f"\t{config.NAME_SCRIPT} debug2 [parameter]|[parameter]|..] Run debug code 2")
|
|
|
|
|
|
# --- MAIN EXECUTION BLOCK ---
|
|
if __name__ == "__main__":
|
|
# Carica la configurazione dal file /etc/piGarden.conf (simulazione)
|
|
# In un'applicazione reale, useresti una libreria per parsare un file .conf o .ini
|
|
# Per ora, assumiamo che la classe Config sia già popolata con i valori di default
|
|
# o che tu la popoli manualmente qui.
|
|
# Esempio di caricamento (molto semplificato):
|
|
# try:
|
|
# with open(config.CONFIG_ETC, 'r') as f:
|
|
# # Parsa il file di configurazione e aggiorna gli attributi di 'config'
|
|
# # Questo richiede un parser INI/JSON/YAML a seconda del formato del tuo file
|
|
# pass
|
|
# except FileNotFoundError:
|
|
# print(f"Config file not found in {config.CONFIG_ETC}", file=sys.stderr)
|
|
# sys.exit(1)
|
|
|
|
# Importa le funzioni del server socket (dal file precedente)
|
|
# Questo richiede che il file socket_server.py sia nello stesso PATH
|
|
# o che tu lo abbia copiato/incollato qui.
|
|
# Per evitare dipendenze circolari o problemi di import,
|
|
# ho copiato le funzioni start_socket_server e stop_socket_server direttamente qui.
|
|
|
|
# Ho bisogno di importare io per catturare l'output di json_status_wrapper
|
|
import io
|
|
|
|
# Funzioni del server socket (copiate dal file precedente per auto-contenimento)
|
|
# Rimuovi questa sezione se preferisci importare da un modulo separato.
|
|
|
|
# Globals per il server socket (dall'altro script)
|
|
TCPSERVER_PID_FILE = config.TCPSERVER_PID_FILE
|
|
TCPSERVER_IP = "0.0.0.0" # Ascolta su tutte le interfacce disponibili
|
|
TCPSERVER_PORT = 12345 # Porta del server socket
|
|
TCPSERVER_USER = config.TCPSERVER_USER # Credenziali opzionali per l'autenticazione
|
|
TCPSERVER_PWD = config.TCPSERVER_PWD
|
|
|
|
class MyTCPHandler(socketserver.BaseRequestHandler):
|
|
def handle(self):
|
|
global RUN_FROM_TCPSERVER
|
|
RUN_FROM_TCPSERVER = True
|
|
client_ip = self.client_address[0]
|
|
log_write("socket_server", "info", f"Nuova connessione da: {client_ip}")
|
|
response_to_client = ""
|
|
|
|
try:
|
|
if TCPSERVER_USER and TCPSERVER_PWD:
|
|
self.request.settimeout(3)
|
|
try:
|
|
user_line = self.request.recv(1024).decode('utf-8').strip()
|
|
password_line = self.request.recv(1024).decode('utf-8').strip()
|
|
except socket.timeout:
|
|
log_write("socket_server", "warning", f"socket connection from: {client_ip} - Timeout during credentials read")
|
|
response_to_client = json_error(0, "Authentication timeout")
|
|
self.request.sendall(response_to_client.encode('utf-8') + b'\n')
|
|
return
|
|
|
|
if user_line != TCPSERVER_USER or password_line != TCPSERVER_PWD:
|
|
log_write("socket_server", "warning", f"socket connection from: {client_ip} - Bad socket server credentials - user:{user_line}")
|
|
response_to_client = json_error(0, "Bad socket server credentials")
|
|
self.request.sendall(response_to_client.encode('utf-8') + b'\n')
|
|
return
|
|
else:
|
|
log_write("socket_server", "info", f"socket connection from: {client_ip} - Authentication successful")
|
|
|
|
self.request.settimeout(None)
|
|
command_line = self.request.recv(4096).decode('utf-8').strip()
|
|
|
|
args = command_line.split(' ')
|
|
# Mappa gli argomenti per facilitare il passaggio alle funzioni
|
|
cmd_args = [args[i] if len(args) > i else "" for i in range(8)]
|
|
|
|
log_write("socket_server", "info", f"socket connection from: {client_ip} - command: {command_line}")
|
|
|
|
# Cattura l'output delle funzioni che stampano su stdout
|
|
old_stdout = sys.stdout
|
|
sys.stdout = captured_output = io.StringIO()
|
|
|
|
# Chiama la funzione di dispatch principale con gli argomenti
|
|
# Questo è il punto dove il server socket chiama le funzioni di piGarden
|
|
dispatch_command(*cmd_args)
|
|
|
|
sys.stdout = old_stdout
|
|
response_to_client = captured_output.getvalue().strip()
|
|
|
|
# Se non c'è una risposta esplicita (es. da json_status), invia un messaggio di successo generico
|
|
if not response_to_client:
|
|
response_to_client = json.dumps({"status": "success", "message": "Command executed."})
|
|
|
|
self.request.sendall(response_to_client.encode('utf-8') + b'\n')
|
|
|
|
except socket.timeout:
|
|
log_write("socket_server", "warning", f"socket connection from: {client_ip} - Timeout waiting for command.")
|
|
response_to_client = json_error(0, "Timeout waiting for command")
|
|
self.request.sendall(response_to_client.encode('utf-8') + b'\n')
|
|
except Exception as e:
|
|
log_write("socket_server", "error", f"Errore durante la gestione della connessione da {client_ip}: {e}")
|
|
response_to_client = json_error(-1, f"Internal server error: {e}")
|
|
self.request.sendall(response_to_client.encode('utf-8') + b'\n')
|
|
finally:
|
|
self.request.close()
|
|
RUN_FROM_TCPSERVER = False
|
|
|
|
def start_socket_server_internal():
|
|
if os.path.exists(TCPSERVER_PID_FILE):
|
|
try:
|
|
os.remove(TCPSERVER_PID_FILE)
|
|
log_write("socket_server", "info", f"Rimosso file PID esistente: {TCPSERVER_PID_FILE}")
|
|
except OSError as e:
|
|
log_write("socket_server", "error", f"Errore nella rimozione del file PID: {e}")
|
|
sys.exit(1)
|
|
|
|
current_pid = os.getpid()
|
|
try:
|
|
with open(TCPSERVER_PID_FILE, "w") as f:
|
|
f.write(str(current_pid))
|
|
log_write("socket_server", "info", f"Server PID {current_pid} scritto in {TCPSERVER_PID_FILE}")
|
|
except IOError as e:
|
|
log_write("socket_server", "error", f"Errore nella scrittura del file PID: {e}")
|
|
sys.exit(1)
|
|
|
|
try:
|
|
server = socketserver.ThreadingTCPServer((TCPSERVER_IP, TCPSERVER_PORT), MyTCPHandler)
|
|
log_write("socket_server", "info", f"Server socket avviato su {TCPSERVER_IP}:{TCPSERVER_PORT}")
|
|
server.serve_forever()
|
|
except Exception as e:
|
|
log_write("socket_server", "error", f"Errore all'avvio del server socket: {e}")
|
|
if os.path.exists(TCPSERVER_PID_FILE):
|
|
os.remove(TCPSERVER_PID_FILE)
|
|
sys.exit(1)
|
|
|
|
def stop_socket_server_internal():
|
|
if not os.path.exists(TCPSERVER_PID_FILE):
|
|
print("Daemon is not running")
|
|
sys.exit(1)
|
|
|
|
log_write("socket_server", "info", "Richiesta di stop del socket server.")
|
|
|
|
with open(TCPSERVER_PID_FILE, "r") as f:
|
|
try:
|
|
pid = int(f.read().strip())
|
|
except ValueError:
|
|
print(f"Errore: Il file PID '{TCPSERVER_PID_FILE}' contiene un PID non valido.")
|
|
sys.exit(1)
|
|
|
|
descendants = list_descendants(pid)
|
|
for d_pid in descendants:
|
|
try:
|
|
os.kill(d_pid, 9)
|
|
log_write("socket_server", "info", f"Terminato processo discendente {d_pid}")
|
|
except ProcessLookupError:
|
|
log_write("socket_server", "info", f"Processo discendente {d_pid} non trovato, probabilmente già terminato.")
|
|
except Exception as e:
|
|
log_write("socket_server", "error", f"Errore durante l'uccisione del processo discendente {d_pid}: {e}")
|
|
|
|
try:
|
|
os.kill(pid, 9)
|
|
log_write("socket_server", "info", f"Terminato processo server {pid}")
|
|
except ProcessLookupError:
|
|
print(f"Processo con PID {pid} non trovato, probabilmente già terminato.")
|
|
except Exception as e:
|
|
log_write("socket_server", "error", f"Errore durante l'uccisione del processo server {pid}: {e}")
|
|
|
|
if os.path.exists(TCPSERVER_PID_FILE):
|
|
try:
|
|
os.remove(TCPSERVER_PID_FILE)
|
|
log_write("socket_server", "info", "File PID rimosso.")
|
|
except OSError as e:
|
|
log_write("socket_server", "error", f"Errore nella rimozione del file PID: {e}")
|
|
|
|
# Funzione di dispatch centrale per i comandi CLI e socket
|
|
def dispatch_command(cmd, *args):
|
|
if cmd == "init":
|
|
initialize()
|
|
elif cmd == "open":
|
|
ev_open(args[0], args[1] if len(args) > 1 else "")
|
|
elif cmd == "open_in":
|
|
ev_open_in(args[0], args[1], args[2], args[3] if len(args) > 3 else "")
|
|
elif cmd == "close":
|
|
ev_close(args[0])
|
|
elif cmd == "list_alias":
|
|
list_alias()
|
|
elif cmd == "ev_status":
|
|
ev_status(args[0])
|
|
elif cmd == "ev_status_all":
|
|
ev_status_all()
|
|
elif cmd == "list_alias_sensor":
|
|
list_alias_sensor()
|
|
elif cmd == "sensor_status":
|
|
sensor_status(args[0], args[1] if len(args) > 1 else None)
|
|
elif cmd == "sensor_status_set":
|
|
sensor_status_set(args[0], args[1], args[2])
|
|
elif cmd == "sensor_status_all":
|
|
sensor_status_all()
|
|
elif cmd == "last_rain_sensor_timestamp":
|
|
last_rain_sensor_timestamp()
|
|
elif cmd == "last_rain_online_timestamp":
|
|
last_rain_online_timestamp()
|
|
elif cmd == "reset_last_rain_sensor_timestamp":
|
|
reset_last_rain_sensor_timestamp()
|
|
message_write("success", "Timestamp of last sensor rain successfully reset")
|
|
json_status_wrapper()
|
|
elif cmd == "reset_last_rain_online_timestamp":
|
|
reset_last_rain_online_timestamp()
|
|
message_write("success", "Timestamp of last online rain successfully reset")
|
|
json_status_wrapper()
|
|
elif cmd == "json_status":
|
|
json_status_wrapper(*args)
|
|
elif cmd == "mqtt_status":
|
|
mqtt_status()
|
|
elif cmd == "check_rain_online":
|
|
check_rain_online()
|
|
message_write("success", "Online rain check performed")
|
|
json_status_wrapper()
|
|
elif cmd == "check_rain_sensor":
|
|
check_rain_sensor()
|
|
message_write("success", "Sensor rain check performed")
|
|
json_status_wrapper()
|
|
elif cmd == "close_all_for_rain":
|
|
# La logica "for_rain" è implicita in close_all se non c'è "force"
|
|
close_all()
|
|
message_write("success", "All solenoid closed for rain")
|
|
json_status_wrapper()
|
|
elif cmd == "close_all":
|
|
close_all(force=(args[0] == "force" if len(args) > 0 else False))
|
|
message_write("success", "All solenoid closed")
|
|
json_status_wrapper()
|
|
elif cmd == "start_socket_server":
|
|
# Il parametro force nello script Bash era per forzare la chiusura se già aperto
|
|
# Qui la logica di start_socket_server_internal già gestisce la rimozione del PID file
|
|
start_socket_server_internal()
|
|
elif cmd == "stop_socket_server":
|
|
stop_socket_server_internal()
|
|
elif cmd == "reboot":
|
|
exec_reboot()
|
|
elif cmd == "poweroff":
|
|
exec_poweroff()
|
|
elif cmd == "set_cron_init":
|
|
vret = set_cron_init()
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_init":
|
|
vret = del_cron_init()
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Cron deleted successful"); json_status_wrapper()
|
|
elif cmd == "set_cron_start_socket_server":
|
|
vret = set_cron_start_socket_server()
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_start_socket_server":
|
|
vret = del_cron_start_socket_server()
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Cron deleted successful"); json_status_wrapper()
|
|
elif cmd == "set_cron_check_rain_sensor":
|
|
vret = set_cron_check_rain_sensor()
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_check_rain_sensor":
|
|
vret = del_cron_check_rain_sensor()
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Cron deleted successful"); json_status_wrapper()
|
|
elif cmd == "set_cron_check_rain_online":
|
|
vret = set_cron_check_rain_online()
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_check_rain_online":
|
|
vret = del_cron_check_rain_online()
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Cron deleted successful"); json_status_wrapper()
|
|
elif cmd == "set_cron_close_all_for_rain":
|
|
vret = set_cron_close_all_for_rain()
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_close_all_for_rain":
|
|
vret = del_cron_close_all_for_rain()
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Cron deleted successful"); json_status_wrapper()
|
|
elif cmd == "add_cron_open":
|
|
vret = add_cron_open(args[0], args[1], args[2], args[3], args[4], args[5], args[6] if len(args) > 6 else "")
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_open":
|
|
vret = del_cron_open(args[0])
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "get_cron_open":
|
|
print(cron_get("open", args[0])) # Stampa diretta
|
|
elif cmd == "del_cron_open_in":
|
|
vret = del_cron_open_in(args[0])
|
|
if vret: json_error(0, "Cron del failed")
|
|
else: message_write("success", "Scheduled start successfully deleted"); json_status_wrapper(f"get_cron_open_in:{args[0]}")
|
|
elif cmd == "add_cron_close":
|
|
vret = add_cron_close(args[0], args[1], args[2], args[3], args[4], args[5], args[6] if len(args) > 6 else "")
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "del_cron_close":
|
|
vret = del_cron_close(args[0])
|
|
if vret: json_error(0, "Cron set failed")
|
|
else: message_write("success", "Cron set successful"); json_status_wrapper()
|
|
elif cmd == "get_cron_close":
|
|
print(cron_get("close", args[0])) # Stampa diretta
|
|
elif cmd == "cmd_pigardensched":
|
|
vret = cmd_pigardensched(*args)
|
|
if vret != 0: # cmd_pigardensched restituisce 0 per successo, 1 per errore
|
|
json_error(0, "piGardenSched command failed")
|
|
log_write("socket_server", "error", f"piGardenSched command failed: {vret}")
|
|
else:
|
|
message_write("success", "Schedule set successful")
|
|
json_status_wrapper()
|
|
elif cmd == "debug1":
|
|
debug1(*args)
|
|
elif cmd == "debug2":
|
|
debug2(*args)
|
|
else:
|
|
show_usage()
|
|
sys.exit(1)
|
|
|
|
# Invio dell'identificativo all'avvio dello script (se non è un comando specifico)
|
|
if len(sys.argv) == 1 or sys.argv[1] not in ["start_socket_server", "stop_socket_server"]:
|
|
send_identifier()
|
|
|
|
# Dispatcher per i comandi dalla riga di comando
|
|
if len(sys.argv) > 1:
|
|
dispatch_command(sys.argv[1], *sys.argv[2:])
|
|
else:
|
|
show_usage()
|
|
sys.exit(1)
|
|
|