02-ago: [proyecto] vigilancia - comando tv_mensaje.py (mensaje parametrizado) + API remota tv_api.py (puerto 8600, via Tailscale) + fix espera FINISHED + AGENTS.md comando tv y Regla 23
This commit is contained in:
21
AGENTS.md
21
AGENTS.md
@@ -90,6 +90,7 @@ Start-Process powershell -WindowStyle Hidden -ArgumentList '-Command', 'python C
|
|||||||
| `ejecuta alarma Twilio` | Modo interactivo: pedir credenciales (o usar .env), 2 telefonos, estrategia (secuencial/paralelo/solo 1/solo 2), ejecutar llamada Twilio con AMD y reportar resultado. Ejecutar con: `python alarma_twilio/cli.py` en cualquier equipo con el repo clonado. |
|
| `ejecuta alarma Twilio` | Modo interactivo: pedir credenciales (o usar .env), 2 telefonos, estrategia (secuencial/paralelo/solo 1/solo 2), ejecutar llamada Twilio con AMD y reportar resultado. Ejecutar con: `python alarma_twilio/cli.py` en cualquier equipo con el repo clonado. |
|
||||||
| `!alarma +numero` desde Telegram | Enviar `!alarma +34657866417` al bot @? (token en .env) desde el iPad/móvil → el VPS lo escucha vía Bot API y ejecuta la alarma. También funciona desde Saved Messages via watcher local. |
|
| `!alarma +numero` desde Telegram | Enviar `!alarma +34657866417` al bot @? (token en .env) desde el iPad/móvil → el VPS lo escucha vía Bot API y ejecuta la alarma. También funciona desde Saved Messages via watcher local. |
|
||||||
| `sync-vaults` | Sincronizar vaults Obsidian: Remotely Save sync → copiar archivos nuevos/actualizados del portátil a sobremesa → forzar OneDrive sync. Ver [[07-Conocimiento/protocolo-sync-vaults-obsidian.md]]. |
|
| `sync-vaults` | Sincronizar vaults Obsidian: Remotely Save sync → copiar archivos nuevos/actualizados del portátil a sobremesa → forzar OneDrive sync. Ver [[07-Conocimiento/protocolo-sync-vaults-obsidian.md]]. |
|
||||||
|
| `tv "mensaje"` | Enviar un mensaje urgente a la tele Google (enciende → 15s → sirena emergencia → mensaje → apagado ADB): `python vigilancia/tv_mensaje.py "mensaje"`. Desde fuera de la WiFi: `curl http://<IP-tailscale>:8600/tv/mensaje?texto=...` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -443,4 +444,24 @@ Ejemplo: `22-jul: [conocimiento] Plan diario Jefe Desarrollo + protocolo guardad
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Regla 23: Mensajes urgentes a la tele (AUTOMATICO)
|
||||||
|
|
||||||
|
El sistema `vigilancia/` permite enviar mensajes de voz a la tele Google de casa. Al usar el comando `tv`, el asistente DEBE:
|
||||||
|
|
||||||
|
1. Ejecutar `python vigilancia/tv_mensaje.py "<mensaje>"` (en el PC que esté en la LAN de casa)
|
||||||
|
2. La secuencia es: **encender la tele en silencio → esperar GOOGLE_TV_ESPERA_SG (15s) → sirena de emergencia → mensaje → apagar por ADB**
|
||||||
|
3. Regla 5: antes de enviar, pedir la clave de doble factor (es un mensaje a la casa)
|
||||||
|
|
||||||
|
**Mensajes urgentes registrados por el usuario:**
|
||||||
|
- Intimidación por límite: *"Por favor, os avisamos de que no podéis cruzar este límite. Tenéis que ir a vuestro cuarto."* (`VOZ_MENSAJE_ALERTA`)
|
||||||
|
|
||||||
|
**Desde fuera de la WiFi** (con Tailscale en el dispositivo): el PC de casa corre `vigilancia/tv_api.py` (puerto 8600), y se llama:
|
||||||
|
```
|
||||||
|
curl "http://<IP-tailscale-del-PC>:8600/tv/mensaje?texto=<mensaje>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Configuración relevante** (`.env` de vigilancia): `GOOGLE_TV_VOLUMEN` (30), `GOOGLE_TV_ESPERA_SG` (15), `GOOGLE_TV_PITIDO_SG` (6), `GOOGLE_TV_APAGAR_VIA_ADB` (1), `ADB_TV_IP` (192.168.50.16:5555), `VOZ_EDGE`/`VOZ_PITCH`/`VOZ_RATE` (voz grave).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
*Creado: 26 Junio 2026 — Actualizado: 30 Jul 2026 (Regla 22: Categorización automática + comando superguardado)*
|
*Creado: 26 Junio 2026 — Actualizado: 30 Jul 2026 (Regla 22: Categorización automática + comando superguardado)*
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ VOZ_RATE=-10%
|
|||||||
GOOGLE_TV_PUERTO=8500
|
GOOGLE_TV_PUERTO=8500
|
||||||
ADB_TV_IP=192.168.50.16:5555
|
ADB_TV_IP=192.168.50.16:5555
|
||||||
GOOGLE_TV_APAGAR_VIA_ADB=1
|
GOOGLE_TV_APAGAR_VIA_ADB=1
|
||||||
|
|
||||||
|
# API remota para enviar mensajes a la tele (tv_api.py). Acceso desde fuera via Tailscale.
|
||||||
|
TV_API_PUERTO=8600
|
||||||
VOZ_MENSAJE_ALERTA=Por favor, os avisamos de que no podéis cruzar este límite. Tenéis que ir a vuestro cuarto.
|
VOZ_MENSAJE_ALERTA=Por favor, os avisamos de que no podéis cruzar este límite. Tenéis que ir a vuestro cuarto.
|
||||||
VOZ_MENSAJE_CONOCIDO={nombres}, ¿qué estás haciendo? No entres ahí.
|
VOZ_MENSAJE_CONOCIDO={nombres}, ¿qué estás haciendo? No entres ahí.
|
||||||
|
|
||||||
|
|||||||
46
vigilancia/tv_api.py
Normal file
46
vigilancia/tv_api.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import threading
|
||||||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
sys_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, sys_dir)
|
||||||
|
|
||||||
|
import config
|
||||||
|
from voz import anuncio_en_teles
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
q = parse_qs(urlparse(self.path).query)
|
||||||
|
texto = (q.get("texto") or q.get("msg") or [""])[0]
|
||||||
|
if not texto:
|
||||||
|
self._resp(400, {"error": "falta parametro texto"})
|
||||||
|
return
|
||||||
|
threading.Thread(target=anuncio_en_teles, args=(texto,), daemon=True).start()
|
||||||
|
print(f"[TV-API] Mensaje lanzado: {texto[:80]}")
|
||||||
|
self._resp(200, {"ok": True, "mensaje": texto})
|
||||||
|
|
||||||
|
def _resp(self, code, obj):
|
||||||
|
body = json.dumps(obj).encode("utf-8")
|
||||||
|
self.send_response(code)
|
||||||
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def log_message(self, *a):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
puerto = int(os.getenv("TV_API_PUERTO", "8600"))
|
||||||
|
print(f"TV API escuchando en :{puerto}")
|
||||||
|
print(f"Uso: http://<ip>:{puerto}/tv/mensaje?texto=Mensaje")
|
||||||
|
HTTPServer(("0.0.0.0", puerto), Handler).serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
21
vigilancia/tv_mensaje.py
Normal file
21
vigilancia/tv_mensaje.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
import config
|
||||||
|
from voz import anuncio_en_teles
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
texto = " ".join(sys.argv[1:])
|
||||||
|
if not texto:
|
||||||
|
print("Uso: python tv_mensaje.py \"mensaje a la tele\"")
|
||||||
|
print("Ejemplo: python tv_mensaje.py \"A cenar, la comida está lista\"")
|
||||||
|
return
|
||||||
|
print(f"Enviando a la tele: {texto}")
|
||||||
|
anuncio_en_teles(texto)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -109,8 +109,8 @@ def _esperar_reproduccion(cast, timeout=15):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _esperar_fin(cast, timeout=90):
|
def _esperar_fin(cast, timeout=120):
|
||||||
"""Espera a que termine (IDLE) despues de haber empezado a reproducir."""
|
"""Espera a que el audio termine de verdad (IDLE + idle_reason FINISHED)."""
|
||||||
mc = cast.media_controller
|
mc = cast.media_controller
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
visto_playing = False
|
visto_playing = False
|
||||||
@@ -119,15 +119,12 @@ def _esperar_fin(cast, timeout=90):
|
|||||||
st = mc.status
|
st = mc.status
|
||||||
if st.player_state == "PLAYING":
|
if st.player_state == "PLAYING":
|
||||||
visto_playing = True
|
visto_playing = True
|
||||||
if st.player_state == "IDLE":
|
if st.player_state == "IDLE" and st.idle_reason == "FINISHED":
|
||||||
if st.idle_reason == "FINISHED":
|
|
||||||
return True
|
|
||||||
if visto_playing:
|
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return False
|
return visto_playing
|
||||||
|
|
||||||
|
|
||||||
def _generar_pitido():
|
def _generar_pitido():
|
||||||
|
|||||||
Reference in New Issue
Block a user