diff --git a/vigilancia/.env.example b/vigilancia/.env.example index fbe2c39..99e2cf2 100644 --- a/vigilancia/.env.example +++ b/vigilancia/.env.example @@ -48,10 +48,15 @@ GOOGLE_TV_ACTIVO=1 GOOGLE_TV_NOMBRES= GOOGLE_TV_VOLUMEN=30 GOOGLE_TV_APAGAR=1 +GOOGLE_TV_PITIDO_ACTIVO=1 +GOOGLE_TV_ESPERA_SG=15 +GOOGLE_TV_PITIDO_SG=6 VOZ_EDGE=es-ES-AlvaroNeural VOZ_PITCH=-15Hz VOZ_RATE=-10% GOOGLE_TV_PUERTO=8500 +ADB_TV_IP=192.168.50.16:5555 +GOOGLE_TV_APAGAR_VIA_ADB=1 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í. diff --git a/vigilancia/config.py b/vigilancia/config.py index 5905119..97a9e81 100644 --- a/vigilancia/config.py +++ b/vigilancia/config.py @@ -69,3 +69,6 @@ ADB_PATH = os.getenv( ) ADB_TV_IP = os.getenv("ADB_TV_IP", "192.168.50.16:5555") GOOGLE_TV_APAGAR_VIA_ADB = os.getenv("GOOGLE_TV_APAGAR_VIA_ADB", "1") == "1" +GOOGLE_TV_PITIDO_ACTIVO = os.getenv("GOOGLE_TV_PITIDO_ACTIVO", "1") == "1" +GOOGLE_TV_ESPERA_SG = int(os.getenv("GOOGLE_TV_ESPERA_SG", "15")) +GOOGLE_TV_PITIDO_SG = float(os.getenv("GOOGLE_TV_PITIDO_SG", "6")) diff --git a/vigilancia/voz.py b/vigilancia/voz.py index b0bb98a..6985a6e 100644 --- a/vigilancia/voz.py +++ b/vigilancia/voz.py @@ -106,13 +106,93 @@ def _esperar_fin(cast, timeout=90): return False +def _generar_pitido(): + """Genera una sirena de emergencia (WAV) si no existe.""" + ruta = os.path.join(MEDIA_DIR, "pitido_emergencia.wav") + if os.path.exists(ruta): + return ruta + try: + import numpy as np + import wave as _wave + + os.makedirs(MEDIA_DIR, exist_ok=True) + sr = 44100 + dur = config.GOOGLE_TV_PITIDO_SG + t = np.linspace(0, dur, int(sr * dur), endpoint=False) + freq = 500 + 400 * np.abs(np.sin(2 * np.pi * 0.5 * t)) + fase = 2 * np.pi * np.cumsum(freq) / sr + y = 0.7 * np.sin(fase) + fade = min(0.2, dur / 4) + n_fade = int(fade * sr) + y[:n_fade] *= np.linspace(0, 1, n_fade) + y[-n_fade:] *= np.linspace(1, 0, n_fade) + data = (y * 32767).astype(np.int16) + with _wave.open(ruta, "w") as w: + w.setnchannels(1) + w.setsampwidth(2) + w.setframerate(sr) + w.writeframes(data.tobytes()) + except Exception as e: + print(f"[Voz] No se pudo generar el pitido: {e}") + return ruta + + +def _generar_silencio(): + """Genera un WAV silencioso corto para encender la tele sin sonido.""" + ruta = os.path.join(MEDIA_DIR, "silencio.wav") + if os.path.exists(ruta): + return ruta + try: + import wave as _wave + + os.makedirs(MEDIA_DIR, exist_ok=True) + with _wave.open(ruta, "w") as w: + w.setnchannels(1) + w.setsampwidth(2) + w.setframerate(8000) + w.writeframes(b"\x00\x00" * (8000 * 2)) + except Exception as e: + print(f"[Voz] No se pudo generar el silencio: {e}") + return ruta + + +def _url_pitido(): + ruta = _generar_pitido() + if not os.path.exists(ruta): + return None + _arrancar_servidor() + if _server is None: + return None + return f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta)}" + + +def _url_silencio(): + ruta = _generar_silencio() + if not os.path.exists(ruta): + return None + _arrancar_servidor() + if _server is None: + return None + return f"http://{_ip_lan()}:{config.GOOGLE_TV_PUERTO}/{os.path.basename(ruta)}" + + +def _reproducir_y_esperar(c, url, content_type, titulo, desc): + c.media_controller.play_media(url, content_type, title=titulo) + c.media_controller.block_until_active(timeout=10) + print(f"[Voz] {desc}") + return _esperar_fin(c) + + def anuncio_en_teles(texto): - """Enciende la tele con el volumen configurado, reproduce el mensaje y la apaga.""" + """Enciende la tele (vol configurado), espera, sirena de emergencia, + mensaje y apaga por ADB.""" if not config.GOOGLE_TV_ACTIVO: return - url = _url_audio(texto) - if url is None: + url_mensaje = _url_audio(texto) + if url_mensaje is None: return + url_pitido = _url_pitido() if config.GOOGLE_TV_PITIDO_ACTIVO else None + url_silencio = _url_silencio() if url_pitido else None try: import pychromecast @@ -133,12 +213,25 @@ def anuncio_en_teles(texto): try: c.wait(timeout=10) c.set_volume(config.GOOGLE_TV_VOLUMEN / 100.0) - c.media_controller.play_media(url, "audio/mpeg", title=texto[:80]) - c.media_controller.block_until_active(timeout=10) - print(f"[Voz] Anuncio en {c.cast_info.friendly_name} " - f"(vol {config.GOOGLE_TV_VOLUMEN})") - if _esperar_fin(c): - print(f"[Voz] Anuncio terminado en {c.cast_info.friendly_name}") + + if url_silencio: + _reproducir_y_esperar( + c, url_silencio, "audio/wav", "inicio", + f"Tele encendida ({c.cast_info.friendly_name})") + + if config.GOOGLE_TV_ESPERA_SG > 0: + print(f"[Voz] Esperando {config.GOOGLE_TV_ESPERA_SG}s...") + time.sleep(config.GOOGLE_TV_ESPERA_SG) + + if url_pitido: + _reproducir_y_esperar( + c, url_pitido, "audio/wav", "pitido", + f"Pitido de emergencia en {c.cast_info.friendly_name}") + + _reproducir_y_esperar( + c, url_mensaje, "audio/mpeg", texto[:80], + f"Anuncio en {c.cast_info.friendly_name} " + f"(vol {config.GOOGLE_TV_VOLUMEN})") if config.GOOGLE_TV_APAGAR_VIA_ADB: from tv_adb import apagar_tv