pyMHCgui/pymhcgui.py
2025-11-26 20:55:38 +01:00

289 lines
11 KiB
Python

import tkinter as tk
from tkinter import messagebox
import threading
import socket
import json
import time
# Variable pour stocker l'adresse IP
server_ip = "127.0.0.1" # Adresse IP par défaut
# Fonction pour envoyer la commande de démarrage ou d'arrêt de la chauffe
def toggle_heating_state(heating_button, current_state):
try:
global server_ip
if server_ip is None:
raise ValueError("L'adresse IP du serveur n'est pas définie.")
port = 4623 # Port du serveur
# Si la chauffe est active (vert), envoyer la commande "stop"
if current_state == "HEATING":
print("stop")
command = "stop"
heating_button.config(bg="gray", text="Chauffe inactive")
# Si la chauffe est inactive (gris), envoyer la commande "start"
elif current_state == "IDLE":
print("start")
command = "start"
heating_button.config(bg="green", text="Chauffe active")
else:
return # Ne rien faire si l'état est inconnu
# Créer la commande à envoyer au serveur
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((server_ip, port))
# Envoi de la commande
s.sendall(command.encode('utf-8'))
print(f"Commande envoyée : {command}")
except Exception as e:
print(f"Erreur lors de l'envoi de la commande de chauffe : {e}")
# Fonction pour mettre à jour la couleur du bouton "Chauffe active"
def update_heating_button(heating_button, state):
if state == "IDLE":
heating_button.config(bg="gray", text="Chauffe inactive")
return "inactive"
elif state == "ERROR":
heating_button.config(bg="red", text="Erreur de chauffe")
return "error"
else:
heating_button.config(bg="green", text="Chauffe active")
return "active"
# Fonction pour récupérer les données depuis la socket
def start_socket_listener(update_widgets):
try:
global server_ip
if server_ip is None:
raise ValueError("L'adresse IP du serveur n'est pas définie.")
port = 4623 # Port du serveur
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((server_ip, port))
while True:
try:
# Envoi de la commande "get_json"
s.sendall(b"get_json")
# Lecture des données
data = s.recv(1024) # Taille max des données reçues
if not data:
break
# Convertir les données en JSON
json_data = json.loads(data.decode('utf-8'))
# Mise à jour des widgets
update_widgets(json_data)
except json.JSONDecodeError:
update_widgets({"error": "Données JSON non valides"})
time.sleep(1)
except Exception as e:
update_widgets({"error": f"Erreur : {e}"})
# Fonction pour mettre à jour l'interface avec les données JSON
def update_interface_with_json(json_data, label, controller_buttons, enabled_buttons, temp_frame, heating_button):
"""
Met à jour les widgets dans l'interface en fonction des données JSON reçues.
"""
try:
# Mise à jour des informations générales
status = json_data.get("status", "Indisponible")
state = json_data.get("state", "Indisponible")
current_temp = json_data.get("t", "Indisponible")
target_temp = json_data.get("target", "Indisponible")
# Texte principal
label.config(
text=f"Status : {status}\n"
f"État : {state}\n"
f"Température actuelle : {current_temp} °C\n"
f"Température cible : {target_temp} °C"
)
# Mise à jour des boutons pour controllers
controllers = json_data.get("controllers", [])
for i, state in enumerate(controllers):
color = "green" if state else "gray"
controller_buttons[i].config(bg=color, text=f"Carte OK {i}")
# Mise à jour des boutons pour enabled
enabled = json_data.get("enabled", [])
for i, state in enumerate(enabled):
color = "green" if state else "gray"
enabled_buttons[i].config(bg=color, text=f"Zone active {i}")
# Mise à jour du tableau des températures
temps = json_data.get("temps", [])
display_temperatures(temps, temp_frame)
# Mise à jour de la couleur du bouton "Chauffe active"
heating_state = json_data.get("state", [])
update_heating_button(heating_button, heating_state)
# Retourner l'état de chauffe actuel
return heating_state
except Exception as e:
label.config(text=f"Erreur d'analyse : {e}")
return "error"
# Fonction pour quitter proprement
def on_close(root):
if messagebox.askokcancel("Quitter", "Voulez-vous vraiment quitter ?"):
root.destroy()
# Fonction pour afficher les températures dans un tableau
def display_temperatures(temps, temp_frame):
for widget in temp_frame.winfo_children():
widget.destroy()
for i in range(0, len(temps), 4):
row = tk.Frame(temp_frame)
row.pack(pady=5)
for j in range(4):
if i + j < len(temps):
temp_label = tk.Label(row, text=f"{temps[i + j]:.1f} °C", width=10)
temp_label.pack(side="left", padx=5)
# Fonction pour ouvrir une fenêtre de modification de la température cible
def open_target_window():
def update_target():
# Récupérer la température entrée par l'utilisateur
try:
new_target = float(target_entry.get())
# Envoyer la commande avec la nouvelle température
command = f"set_target {new_target}"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((server_ip, 4623))
s.sendall(command.encode('utf-8'))
# Fermer la fenêtre après avoir envoyé la commande
target_window.destroy()
except ValueError:
messagebox.showerror("Erreur", "Veuillez entrer une température valide.")
# Création de la fenêtre pour changer la température cible
target_window = tk.Toplevel()
target_window.title("Modifier Température Cible")
target_window.geometry("300x150")
# Ajouter un champ de saisie pour la nouvelle température
target_label = tk.Label(target_window, text="Nouvelle température cible :")
target_label.pack(pady=10)
target_entry = tk.Entry(target_window)
target_entry.pack(pady=5)
# Bouton pour valider la température
confirm_button = tk.Button(target_window, text="Valider", command=update_target)
confirm_button.pack(pady=10)
# Fonction pour activer ou désactiver une zone en envoyant une commande
def toggle_zone(zone_number, enabled_buttons):
try:
global server_ip
if server_ip is None:
raise ValueError("L'adresse IP du serveur n'est pas définie.")
# Vérifier l'état actuel de la zone
current_state = enabled_buttons[zone_number].cget("bg")
new_state = 1 if current_state == "gray" else 0
# Envoi de la commande "set_zone"
command = f"set_zone {zone_number} {new_state}"
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((server_ip, 4623))
s.sendall(command.encode('utf-8'))
# Mettre à jour la couleur du bouton en fonction de l'état
new_color = "green" if new_state == 1 else "gray"
enabled_buttons[zone_number].config(bg=new_color, text=f"Zone active {zone_number}")
except Exception as e:
print(f"Erreur lors du changement de l'état de la zone {zone_number}: {e}")
# Fenêtre de configuration de l'adresse IP
def configure_ip():
def submit_ip():
global server_ip
ip = ip_entry.get()
try:
# Vérification simple de l'adresse IP (vous pouvez ajouter une validation plus complète)
socket.inet_aton(ip)
server_ip = ip
ip_window.destroy()
create_app() # Lancer l'application principale
except socket.error:
messagebox.showerror("Erreur", "Adresse IP invalide. Veuillez entrer une adresse IP valide.")
ip_window = tk.Tk()
ip_window.title("Configuration Adresse IP")
ip_window.geometry("400x200")
ip_label = tk.Label(ip_window, text="Entrez l'adresse IP du serveur :")
ip_label.pack(pady=10)
ip_entry = tk.Entry(ip_window)
ip_entry.pack(pady=5)
ip_entry.insert(0, server_ip) # Remplir avec l'IP par défaut
submit_button = tk.Button(ip_window, text="Valider", command=submit_ip)
submit_button.pack(pady=20)
ip_window.mainloop()
# Fonction principale pour créer l'interface
def create_app():
# Créer la fenêtre principale
root = tk.Tk()
root.title("MHC Client")
root.geometry("800x600")
# Frame pour afficher les informations générales
label = tk.Label(root, text="Données non reçues", font=("Arial", 12))
label.pack(pady=20)
# Créer des boutons pour les contrôleurs (Carte OK)
controller_buttons = []
controller_frame = tk.Frame(root)
controller_frame.pack(pady=10)
for i in range(2):
button = tk.Button(controller_frame, text=f"Carte OK {i}", width=15, height=2)
button.pack(side="left", padx=10)
controller_buttons.append(button)
# Créer des boutons pour les zones
enabled_buttons = []
enabled_frame = tk.Frame(root)
enabled_frame.pack(pady=10)
for i in range(4):
button = tk.Button(enabled_frame, text=f"Zone active {i}", width=15, height=2, command=lambda i=i: toggle_zone(i, enabled_buttons))
button.pack(side="left", padx=10)
enabled_buttons.append(button)
# Frame pour afficher les températures
temp_frame = tk.Frame(root)
temp_frame.pack(pady=20)
# Bouton pour changer la température cible
target_button = tk.Button(root, text="Changer la température cible", command=open_target_window)
target_button.pack(pady=10)
# Bouton pour gérer la chauffe
heating_button = tk.Button(root, text="Chauffe inactive", bg="gray", width=15, height=2)
heating_button.pack(pady=10)
# Fonction de mise à jour des widgets après chaque lecture
def update_widgets(json_data):
heating_state = update_interface_with_json(json_data, label, controller_buttons, enabled_buttons, temp_frame, heating_button)
return heating_state
# Démarrer un thread pour écouter les données de la socket
threading.Thread(target=start_socket_listener, args=(update_widgets,), daemon=True).start()
# Fonction pour quitter proprement
root.protocol("WM_DELETE_WINDOW", lambda: on_close(root))
# Boucle principale de l'application
root.mainloop()
if __name__ == "__main__":
configure_ip() # Ouvrir la fenêtre de configuration de l'IP avant de lancer l'application