289 lines
11 KiB
Python
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
|