04-oct: superguardado automático
This commit is contained in:
174
scripts/encender_sobremesa.py
Normal file
174
scripts/encender_sobremesa.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/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())
|
||||
21
scripts/ssh_sobremesa.ps1
Normal file
21
scripts/ssh_sobremesa.ps1
Normal file
@@ -0,0 +1,21 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Abre una sesion PowerShell remota en el sobremesa por SSH (sobre Tailscale).
|
||||
|
||||
.DESCRIPTION
|
||||
Se conecta a `juanjo@100.75.115.29` (sobremesa, DESKTOP-M8SE61G) y cae en su
|
||||
PowerShell. Requiere que el sobremesa este encendido (ver
|
||||
`scripts/encender_sobremesa.py`) y Tailscale activo en ambos equipos.
|
||||
|
||||
.EXAMPLE
|
||||
powershell -ExecutionPolicy Bypass -File scripts\ssh_sobremesa.ps1
|
||||
#>
|
||||
param(
|
||||
[string]$Maquina = "100.75.115.29",
|
||||
[string]$Usuario = "juanjo"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Write-Host "Conectando a PowerShell del sobremesa ($Usuario@$Maquina)..." -ForegroundColor Cyan
|
||||
Write-Host "Para salir: escribe 'exit'." -ForegroundColor DarkGray
|
||||
ssh "$Usuario@$Maquina"
|
||||
@@ -4,9 +4,11 @@
|
||||
Genera y reproduce un mensaje hablado con VoiceStudio (http://127.0.0.1:3900)
|
||||
eligiendo el perfil de voz segun el TIPO de mensaje:
|
||||
|
||||
default -> Carolina (10466680) Mensajes trascendentes de OpenCode (por defecto)
|
||||
default -> Carolina (f40515ca) Mensajes trascendentes de OpenCode (por defecto)
|
||||
biblioteca -> Juanjo (4f18d2df) Mensajes de la biblioteca Prolongo
|
||||
critico -> Abuelo (9f83c527) Mensajes importantes / criticos
|
||||
critico -> Abuelo (e0ecda0a) Mensajes importantes / criticos
|
||||
|
||||
Todas las voces son NATIVAS EN CASTELLANO (language=es). Perfiles en VoiceStudio.
|
||||
|
||||
Uso:
|
||||
python voz_mensaje.py "texto a decir" [--tipo default|biblioteca|critico]
|
||||
@@ -28,9 +30,9 @@ BASE = os.environ.get("VOICESTUDIO_BASE", "http://127.0.0.1:3900")
|
||||
|
||||
# tipo -> (profile_id, nombre)
|
||||
PROFILES = {
|
||||
"default": ("10466680", "Carolina"),
|
||||
"default": ("f40515ca", "Carolina"),
|
||||
"biblioteca": ("4f18d2df", "Juanjo"),
|
||||
"critico": ("9f83c527", "Abuelo"),
|
||||
"critico": ("e0ecda0a", "Abuelo"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user