#!/usr/bin/env python3 # -*- coding: utf-8 -*- """encender_sobremesa.py — Protocolo de encendido remoto del sobremesa. Pasos (protocolo acordado): 0. Estado de la regleta (best-effort, si hay acceso al Tapo). 1. Comprobar si el PC esta apagado (Tailscale). 2. Si esta apagado -> encender la regleta con un comando a Alexa (via Home Assistant). 3. Esperar 3 min. 4. Volver a comprobar. Si esta encendido -> fin. 5. Si sigue apagado -> intentar Wake-on-LAN (best-effort). 6. Esperar 3 min, comprobar e informar. Al terminar cada comprobacion emite un aviso de voz (Carolina) para que el usuario pueda hacer otras cosas mientras tanto. Uso: python scripts/encender_sobremesa.py [--espera 180] [--no-voz] """ from __future__ import annotations import argparse import glob import os import re import subprocess import sys import time from pathlib import Path _SCRIPTS_DIR = Path(__file__).resolve().parent if str(_SCRIPTS_DIR) not in sys.path: sys.path.insert(0, str(_SCRIPTS_DIR)) import infra_paths # noqa: E402 TS_EXE = os.path.join(os.environ.get("ProgramFiles", r"C:\Program Files"), "Tailscale", "tailscale.exe") NODO = "desktop-m8se61g" TAILSCALE_IP = "100.75.115.29" MAC = "74:56:3C:CB:0E:CE" COMANDO_REGLETA = "enciende regleta" # --------------------------------------------------------------- estado del PC def pc_encendido() -> bool | None: """True/False segun Tailscale. None si no se puede determinar.""" try: out = subprocess.run([TS_EXE, "status"], capture_output=True, text=True, timeout=20).stdout except Exception: return None for linea in out.splitlines(): if NODO in linea: return "offline" not in linea return None # --------------------------------------------------------------- regleta (Tapo) def estado_regleta() -> bool | None: """Best-effort: True/False si se puede consultar el Tapo; None si no.""" try: from PyP100 import PyP100 as PP except Exception: return None # credenciales hardcodeadas en un script existente del repo de negocio repo = infra_paths.biblioteca_repo() creds = None for f in glob.glob(str(repo / "scripts" / "protocolo_apagado*.py")) + glob.glob(str(_SCRIPTS_DIR / "protocolo_apagado*.py")): try: s = open(f, encoding="utf-8", errors="replace").read() except Exception: continue m = re.search(r"P100\(\s*'([^']+)'\s*,\s*'([^']+)'\s*,\s*'([^']+)'\s*\)", s) if m: creds = m.groups() break if not creds: return None try: d = PP.P100(*creds) d.handshake() d.login() return bool(d.getDeviceInfo().get("result", {}).get("device_on")) except Exception: return None # --------------------------------------------------------------- Alexa / HA def encender_regleta() -> bool: """Envia 'enciende regleta' a Alexa vía Home Assistant.""" infra_paths.bootstrap_biblioteca_paths() try: from ha_alexa import command return command(COMANDO_REGLETA) except Exception as e: print(f"[encender_sobremesa] Error llamando a HA: {e}", file=sys.stderr) return False # --------------------------------------------------------------- WoL def wol() -> None: """Magic packet (best-effort). Desde .50 no cruza a .1; se intenta igualmente.""" import socket paquete = b"\xff" * 6 + bytes.fromhex(MAC.replace(":", "")) * 16 for destino in (f"{TAILSCALE_IP.split('.')[0]}.255", "255.255.255.255", "192.168.1.255", "192.168.50.255"): try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.sendto(paquete, (destino, 9)) s.close() print(f" magic packet -> {destino}:9") except Exception as e: print(f" magic packet -> {destino}:9 ERROR {e}") # --------------------------------------------------------------- voz def aviso(texto: str, voz: bool = True) -> None: print(f"[aviso] {texto}") if not voz: return try: subprocess.run([sys.executable, str(_SCRIPTS_DIR / "voz_mensaje.py"), texto, "--tipo", "default"], check=False, timeout=120) except Exception: pass # --------------------------------------------------------------- protocolo def main() -> int: ap = argparse.ArgumentParser(description="Encendido remoto del sobremesa.") ap.add_argument("--espera", type=int, default=180, help="Segundos de espera entre comprobaciones (defecto 180).") ap.add_argument("--no-voz", action="store_true", help="Sin avisos de voz.") args = ap.parse_args() voz = not args.no_voz reg = estado_regleta() print(f"Regleta: {'ENCENDIDA' if reg else 'APAGADA' if reg is False else 'desconocido'}") st = pc_encendido() print(f"PC: {'encendido' if st else 'apagado' if st is False else 'desconocido'}") if st: aviso("El sobremesa ya estaba encendido.", voz) return 0 print(f"-> Encendiendo regleta (Alexa: '{COMANDO_REGLETA}')... {encender_regleta()}") print(f"-> Esperando {args.espera}s...") time.sleep(args.espera) if pc_encendido(): aviso("El sobremesa se ha encendido correctamente con la regleta.", voz) return 0 print("-> Sigue apagado; intentando Wake-on-LAN (best-effort)...") wol() print(f"-> Esperando {args.espera}s...") time.sleep(args.espera) if pc_encendido(): aviso("El sobremesa se ha encendido tras el Wake on LAN.", voz) return 0 aviso("No se ha podido encender el sobremesa ni con la regleta ni con Wake on LAN.", voz) return 1 if __name__ == "__main__": raise SystemExit(main())