mirror of
https://github.com/Ladebeze66/tuya_project.git
synced 2025-12-15 19:26:57 +01:00
257 lines
8.6 KiB
Python
257 lines
8.6 KiB
Python
"""
|
|
Utilitaires partagés pour les scripts de contrôle des appareils Tuya.
|
|
Ce module fournit des fonctions communes utilisées par tous les scripts.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import time
|
|
import logging
|
|
import tinytuya
|
|
from . import config
|
|
|
|
# Configuration du logging
|
|
logging.basicConfig(
|
|
level=logging.INFO if config.VERBOSE_MODE else logging.WARNING,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[logging.StreamHandler()]
|
|
)
|
|
logger = logging.getLogger("TuyaControl")
|
|
|
|
def load_device_info(device_id):
|
|
"""
|
|
Charge les informations d'un appareil depuis le fichier devices.json
|
|
|
|
Args:
|
|
device_id (str): ID de l'appareil Tuya
|
|
|
|
Returns:
|
|
dict: Informations de l'appareil ou None si non trouvé
|
|
"""
|
|
try:
|
|
# Chemin vers le répertoire parent du projet
|
|
base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
devices_file = os.path.join(base_dir, 'devices.json')
|
|
|
|
with open(devices_file, 'r') as f:
|
|
devices = json.load(f)
|
|
|
|
for device in devices:
|
|
if device['id'] == device_id:
|
|
return device
|
|
|
|
logger.error(f"Appareil avec ID {device_id} non trouvé dans devices.json")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors du chargement des informations de l'appareil: {e}")
|
|
return None
|
|
|
|
def connect_device(device_id, device_ip=None, device_key=None, device_version=3.3):
|
|
"""
|
|
Se connecte à un appareil Tuya
|
|
|
|
Args:
|
|
device_id (str): ID de l'appareil
|
|
device_ip (str, optional): IP de l'appareil (facultatif si mode scan)
|
|
device_key (str, optional): Clé de l'appareil (facultatif si chargé depuis devices.json)
|
|
device_version (float, optional): Version du protocole
|
|
|
|
Returns:
|
|
tinytuya.Device: Instance de l'appareil connecté ou None en cas d'erreur
|
|
"""
|
|
try:
|
|
# Si la clé n'est pas fournie, charger depuis devices.json
|
|
if device_key is None:
|
|
device_info = load_device_info(device_id)
|
|
if not device_info:
|
|
logger.error(f"Impossible de trouver les informations pour l'appareil {device_id}")
|
|
return None
|
|
device_key = device_info.get('key')
|
|
|
|
# Créer l'objet appareil
|
|
device = tinytuya.Device(
|
|
dev_id=device_id,
|
|
address=device_ip, # None si on veut que la bibliothèque recherche l'appareil
|
|
local_key=device_key,
|
|
version=device_version
|
|
)
|
|
|
|
# Configurer le timeout
|
|
device.set_socketTimeout(config.TIMEOUT)
|
|
|
|
# Tester la connexion
|
|
status = device.status()
|
|
if 'Error' in status:
|
|
logger.error(f"Erreur de connexion à l'appareil {device_id}: {status['Error']}")
|
|
return None
|
|
|
|
logger.info(f"Connexion réussie à l'appareil {device_id}")
|
|
return device
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la connexion à l'appareil {device_id}: {e}")
|
|
return None
|
|
|
|
def toggle_device(device, switch_id="1", current_state=None):
|
|
"""
|
|
Bascule l'état d'un appareil (allumé/éteint)
|
|
|
|
Args:
|
|
device (tinytuya.Device): Instance de l'appareil
|
|
switch_id (str): ID du commutateur (généralement "1" ou "20" pour les lumières)
|
|
current_state (bool, optional): État actuel si connu, sinon sera détecté
|
|
|
|
Returns:
|
|
bool: Nouvel état (True=On, False=Off) ou None en cas d'erreur
|
|
"""
|
|
try:
|
|
# Si l'état actuel n'est pas fourni, l'obtenir
|
|
if current_state is None:
|
|
status = device.status()
|
|
if 'dps' in status and switch_id in status['dps']:
|
|
current_state = status['dps'][switch_id]
|
|
else:
|
|
logger.error(f"Impossible de déterminer l'état actuel du commutateur {switch_id}")
|
|
return None
|
|
|
|
# Basculer l'état (inverse de l'état actuel)
|
|
new_state = not current_state
|
|
result = device.set_value(switch_id, new_state)
|
|
|
|
logger.info(f"Appareil basculé de {current_state} à {new_state}")
|
|
return new_state
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors du basculement de l'appareil: {e}")
|
|
return None
|
|
|
|
def control_shutter(device, action="stop"):
|
|
"""
|
|
Contrôle un volet roulant ou store
|
|
|
|
Args:
|
|
device (tinytuya.Device): Instance de l'appareil
|
|
action (str): Action à effectuer ("stop", "up"/"forward", "down"/"back")
|
|
|
|
Returns:
|
|
bool: True si réussi, False sinon
|
|
"""
|
|
try:
|
|
# Convertir les actions en commandes spécifiques au dispositif
|
|
command = action
|
|
if action == "up":
|
|
command = "forward"
|
|
elif action == "down":
|
|
command = "back"
|
|
|
|
# Les volets utilisent généralement le point de données "1" pour leurs commandes
|
|
result = device.set_value("1", command)
|
|
|
|
logger.info(f"Volet contrôlé avec commande: {command}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors du contrôle du volet: {e}")
|
|
return False
|
|
|
|
def get_device_status_text(device_id, status=None):
|
|
"""
|
|
Obtient un texte descriptif de l'état de l'appareil
|
|
|
|
Args:
|
|
device_id (str): ID de l'appareil
|
|
status (dict, optional): État actuel si déjà obtenu
|
|
|
|
Returns:
|
|
str: Description textuelle de l'état
|
|
"""
|
|
try:
|
|
device_info = load_device_info(device_id)
|
|
if not device_info:
|
|
return "Appareil inconnu"
|
|
|
|
# Si le statut n'est pas fourni, essayer de se connecter et obtenir l'état
|
|
if status is None:
|
|
device = connect_device(device_id)
|
|
if device:
|
|
status = device.status()
|
|
else:
|
|
return "Non connecté"
|
|
|
|
# Obtenir le nom de l'appareil
|
|
device_name = device_info.get('name', 'Appareil')
|
|
|
|
# Analyser l'état selon le type d'appareil
|
|
if 'dps' in status:
|
|
dps = status['dps']
|
|
|
|
# Pour les prises et la plupart des appareils on/off
|
|
if '1' in dps and isinstance(dps['1'], bool):
|
|
state = "ALLUMÉ" if dps['1'] else "ÉTEINT"
|
|
return f"{device_name}: {state}"
|
|
|
|
# Pour les lumières (généralement dps 20)
|
|
elif '20' in dps and isinstance(dps['20'], bool):
|
|
state = "ALLUMÉE" if dps['20'] else "ÉTEINTE"
|
|
return f"{device_name}: {state}"
|
|
|
|
# Pour les volets
|
|
elif '1' in dps and isinstance(dps['1'], str):
|
|
states = {
|
|
"stop": "ARRÊTÉ",
|
|
"forward": "EN OUVERTURE",
|
|
"back": "EN FERMETURE"
|
|
}
|
|
state = states.get(dps['1'], dps['1'].upper())
|
|
return f"{device_name}: {state}"
|
|
|
|
return f"{device_name}: État inconnu"
|
|
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de l'obtention de l'état de l'appareil: {e}")
|
|
return "Erreur"
|
|
|
|
def scan_for_devices():
|
|
"""
|
|
Recherche les appareils Tuya disponibles sur le réseau local
|
|
|
|
Returns:
|
|
list: Liste des appareils trouvés
|
|
"""
|
|
try:
|
|
logger.info("Recherche des appareils Tuya sur le réseau...")
|
|
devices = tinytuya.scan()
|
|
if devices is None:
|
|
devices = []
|
|
logger.info(f"Trouvé {len(devices)} appareils")
|
|
return devices
|
|
except Exception as e:
|
|
logger.error(f"Erreur lors de la recherche d'appareils: {e}")
|
|
return []
|
|
|
|
# Fonction pour l'intégration avec StreamDeck
|
|
def report_for_streamdeck(result, device_name="Appareil", new_state=None):
|
|
"""
|
|
Génère un rapport formaté pour l'intégration avec StreamDeck
|
|
|
|
Args:
|
|
result (bool): Résultat de l'opération
|
|
device_name (str): Nom de l'appareil
|
|
new_state: Nouvel état (True=On, False=Off, str pour d'autres états)
|
|
|
|
Returns:
|
|
dict: Rapport formaté
|
|
"""
|
|
# Format attendu par StreamDeck
|
|
report = {
|
|
"success": result is not None and result is not False,
|
|
"device": device_name,
|
|
"state": str(new_state) if new_state is not None else "unknown",
|
|
"timestamp": time.time()
|
|
}
|
|
|
|
# Afficher le rapport au format JSON pour StreamDeck
|
|
print(json.dumps(report))
|
|
return report |