04-oct: encender_sobremesa escalera WoL->regleta->ciclo (Power On Loss validado) + doc

This commit is contained in:
minguezsanzjuanjose
2026-10-04 00:48:29 +02:00
parent 80bd93f821
commit d5df0e3f05
4 changed files with 82 additions and 56 deletions

View File

@@ -2,20 +2,16 @@
# -*- 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.
Escalera de intentos (acordada):
0. Estado de la regleta (best-effort).
1. Si el PC esta apagado -> intento 1: Wake-on-LAN.
2. Si no responde -> intento 2: encender la regleta (comando Alexa via Home Assistant).
3. Si no responde -> intento 3: apagar la regleta 1 min y volver a encenderla
(BIOS 'Restore on AC Power Loss = Power On' -> arranca solo).
4. Informar por voz (Carolina) tras cada comprobacion.
Uso:
python scripts/encender_sobremesa.py [--espera 180] [--no-voz]
python scripts/encender_sobremesa.py [--espera 120] [--no-voz]
"""
from __future__ import annotations
@@ -36,13 +32,11 @@ 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"
COS_ENCENDER = "enciende regleta"
COS_APAGAR = "apaga regleta"
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:
@@ -55,15 +49,12 @@ def pc_encendido() -> bool | None:
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")):
@@ -86,28 +77,29 @@ def estado_regleta() -> bool | None:
return None
# --------------------------------------------------------------- Alexa / HA
def encender_regleta() -> bool:
"""Envia 'enciende regleta' a Alexa vía Home Assistant."""
def _comando_alexa(texto: str) -> bool:
infra_paths.bootstrap_biblioteca_paths()
try:
from ha_alexa import command
return command(COMANDO_REGLETA)
return command(texto)
except Exception as e:
print(f"[encender_sobremesa] Error llamando a HA: {e}", file=sys.stderr)
return False
# --------------------------------------------------------------- WoL
def encender_regleta() -> bool:
return _comando_alexa(COS_ENCENDER)
def apagar_regleta() -> bool:
return _comando_alexa(COS_APAGAR)
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"):
for destino in ("192.168.1.255", "192.168.1.239", "255.255.255.255", "192.168.50.255"):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
@@ -118,7 +110,15 @@ def wol() -> None:
print(f" magic packet -> {destino}:9 ERROR {e}")
# --------------------------------------------------------------- voz
def esperar_encendido(segundos: int) -> bool:
t = 0
while t < segundos:
if pc_encendido():
return True
time.sleep(10)
t += 10
return False
def aviso(texto: str, voz: bool = True) -> None:
print(f"[aviso] {texto}")
@@ -131,11 +131,9 @@ def aviso(texto: str, voz: bool = True) -> None:
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("--espera", type=int, default=120, help="Segundos por intento (defecto 120).")
ap.add_argument("--no-voz", action="store_true", help="Sin avisos de voz.")
args = ap.parse_args()
voz = not args.no_voz
@@ -149,24 +147,30 @@ def main() -> int:
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)...")
# Intento 1 - Wake-on-LAN
print(f"-> Intento 1: Wake-on-LAN...")
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)
if esperar_encendido(args.espera):
aviso("El sobremesa se ha encendido con Wake on LAN.", voz)
return 0
aviso("No se ha podido encender el sobremesa ni con la regleta ni con Wake on LAN.", voz)
# Intento 2 - encender regleta
print("-> Intento 2: encender regleta (Alexa)...")
encender_regleta()
if esperar_encendido(args.espera):
aviso("El sobremesa se ha encendido al dar corriente la regleta.", voz)
return 0
# Intento 3 - ciclo de regleta (Power On Loss)
print("-> Intento 3: apagar regleta 1 min y volver a encender (Power On Loss)...")
apagar_regleta()
time.sleep(60)
encender_regleta()
if esperar_encendido(args.espera):
aviso("El sobremesa se ha encendido tras el ciclo de regleta. Power On Loss funciona.", voz)
return 0
aviso("No se ha podido encender el sobremesa ni con WoL ni con la regleta.", voz)
return 1