05-sep: [tapo] controlar termo desde Telegram
This commit is contained in:
@@ -93,3 +93,23 @@ Estas mejoras quedan apuntadas para la Raspberry actual, sin implementarlas ahor
|
|||||||
- Despliegue y actualizacion remota del codigo.
|
- Despliegue y actualizacion remota del codigo.
|
||||||
- Capturas de camaras y control de dispositivos conectados.
|
- Capturas de camaras y control de dispositivos conectados.
|
||||||
- Comando `/flota` preparado para cuando existan varias Raspberry.
|
- Comando `/flota` preparado para cuando existan varias Raspberry.
|
||||||
|
|
||||||
|
## Primer dispositivo Tapo
|
||||||
|
|
||||||
|
El dispositivo `termo` es un Tapo Smart Wi-Fi Plug P100. El bot dispone de estos comandos directos:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/termo on
|
||||||
|
/termo off
|
||||||
|
/termo estado
|
||||||
|
```
|
||||||
|
|
||||||
|
La Raspberry necesita estas variables en `/home/pi/minipc/vigilancia/.env`, introducidas localmente y sin publicarlas:
|
||||||
|
|
||||||
|
```text
|
||||||
|
TAPO_USERNAME=cuenta-de-tapo
|
||||||
|
TAPO_PASSWORD=contrasena-de-tapo
|
||||||
|
TAPO_TERMO_IP=192.168.50.6
|
||||||
|
```
|
||||||
|
|
||||||
|
El control usa `python-kasa` directamente desde la Raspberry y no depende de Alexa.
|
||||||
|
|||||||
@@ -33,3 +33,10 @@
|
|||||||
- **Resultado:** OK
|
- **Resultado:** OK
|
||||||
- **Decisiones:** Ethernet queda como ruta preferida, Wi-Fi como respaldo; se crea `raspberry_info.sh` como comando unico de diagnostico sin secretos.
|
- **Decisiones:** Ethernet queda como ruta preferida, Wi-Fi como respaldo; se crea `raspberry_info.sh` como comando unico de diagnostico sin secretos.
|
||||||
- **Gotchas/Lecciones:** El servicio correcto es `autossh-tunel.service`; la Pi tiene Raspbian 13 y dos IPs activas (`192.168.50.187` y `192.168.50.251`).
|
- **Gotchas/Lecciones:** El servicio correcto es `autossh-tunel.service`; la Pi tiene Raspbian 13 y dos IPs activas (`192.168.50.187` y `192.168.50.251`).
|
||||||
|
|
||||||
|
### [16:20] Inicio de control directo Tapo
|
||||||
|
- **Que:** Preparar el primer dispositivo Telegram -> Raspberry -> Tapo sin Alexa.
|
||||||
|
- **Como:** Se instalo `python-kasa`, se descubrio el enchufe `termo` por MAC y se encontro en `192.168.50.6`.
|
||||||
|
- **Resultado:** Implementacion desplegada; pendiente de credenciales Tapo locales.
|
||||||
|
- **Decisiones:** Comandos permitidos: `/termo on`, `/termo off` y `/termo estado`.
|
||||||
|
- **Gotchas/Lecciones:** La cuenta Tapo y su contrasena no se guardan en el repositorio; deben introducirse directamente en el `.env` de la Pi.
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ CAM_2_RTSP=rtsp://usuario:password@192.168.1.51:554/stream2
|
|||||||
TELEGRAM_TOKEN=123456789:AAExampleTokenAqui
|
TELEGRAM_TOKEN=123456789:AAExampleTokenAqui
|
||||||
TELEGRAM_CHAT_ID=987654321
|
TELEGRAM_CHAT_ID=987654321
|
||||||
|
|
||||||
|
# Tapo directo desde la Raspberry (no compartir la contrasena)
|
||||||
|
TAPO_USERNAME=
|
||||||
|
TAPO_PASSWORD=
|
||||||
|
TAPO_TERMO_IP=
|
||||||
|
|
||||||
# Franja horaria activa (formato 24h)
|
# Franja horaria activa (formato 24h)
|
||||||
HORA_INICIO=08
|
HORA_INICIO=08
|
||||||
HORA_FIN=17
|
HORA_FIN=17
|
||||||
|
|||||||
@@ -369,6 +369,20 @@ def procesar(texto, chat_id):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"descargar error: {e}")
|
log(f"descargar error: {e}")
|
||||||
enviar(f"❌ Error en Download Station: {e}", chat_id)
|
enviar(f"❌ Error en Download Station: {e}", chat_id)
|
||||||
|
elif comando == "termo":
|
||||||
|
accion = argumento.lower().strip()
|
||||||
|
acciones = {"on": "on", "encender": "on", "off": "off",
|
||||||
|
"apagar": "off", "estado": "status", "status": "status"}
|
||||||
|
if accion not in acciones:
|
||||||
|
enviar("Uso: <code>/termo on</code>, <code>/termo off</code> o <code>/termo estado</code>", chat_id)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from tapo_control import controlar_termo
|
||||||
|
resultado = controlar_termo(acciones[accion])
|
||||||
|
enviar(f"🔌 <b>{resultado}</b>", chat_id)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"termo error: {e}")
|
||||||
|
enviar(f"❌ Error controlando el termo: {e}", chat_id)
|
||||||
elif t in ("pelis", "/pelis"):
|
elif t in ("pelis", "/pelis"):
|
||||||
procesar_pelis(chat_id)
|
procesar_pelis(chat_id)
|
||||||
elif t in ("series", "/series"):
|
elif t in ("series", "/series"):
|
||||||
@@ -413,7 +427,9 @@ def procesar(texto, chat_id):
|
|||||||
"<b>8.</b> <code>descargas</code> — panel con progreso y botones\n"
|
"<b>8.</b> <code>descargas</code> — panel con progreso y botones\n"
|
||||||
" ✏️ Ej: <code>/descargas</code> o <code>/descargar</code>\n"
|
" ✏️ Ej: <code>/descargas</code> o <code>/descargar</code>\n"
|
||||||
"<b>9.</b> <code>descargar <enlace></code> — añade a Download Station\n"
|
"<b>9.</b> <code>descargar <enlace></code> — añade a Download Station\n"
|
||||||
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n\n"
|
" ✏️ Ej: <code>/descargar magnet:?xt=urn:btih:...</code>\n"
|
||||||
|
"<b>10.</b> <code>termo on|off|estado</code> — controla el enchufe Tapo\n"
|
||||||
|
" ✏️ Ej: <code>/termo estado</code>\n\n"
|
||||||
"━━━ 📺 TV ━━━\n"
|
"━━━ 📺 TV ━━━\n"
|
||||||
"<b>10.</b> <code>tv <mensaje></code> — mensaje rápido a la tele\n"
|
"<b>10.</b> <code>tv <mensaje></code> — mensaje rápido a la tele\n"
|
||||||
" ✏️ Ej: <code>/tv la cena está lista</code>\n"
|
" ✏️ Ej: <code>/tv la cena está lista</code>\n"
|
||||||
|
|||||||
76
vigilancia/tapo_control.py
Normal file
76
vigilancia/tapo_control.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""Control directo de dispositivos Tapo desde la Raspberry Pi."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
|
||||||
|
from kasa import Discover
|
||||||
|
|
||||||
|
|
||||||
|
def _load_env():
|
||||||
|
values = {}
|
||||||
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
|
||||||
|
try:
|
||||||
|
with open(path) as stream:
|
||||||
|
for line in stream:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith("#") and "=" in line:
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
values[key.strip()] = value.strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
FILE_ENV = _load_env()
|
||||||
|
|
||||||
|
|
||||||
|
def _env(name):
|
||||||
|
return os.getenv(name, FILE_ENV.get(name, "")).strip()
|
||||||
|
|
||||||
|
|
||||||
|
async def _device():
|
||||||
|
host = _env("TAPO_TERMO_IP")
|
||||||
|
username = _env("TAPO_USERNAME")
|
||||||
|
password = _env("TAPO_PASSWORD")
|
||||||
|
if not host or not username or not password:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Falta configurar TAPO_TERMO_IP, TAPO_USERNAME y TAPO_PASSWORD en .env"
|
||||||
|
)
|
||||||
|
return await Discover.discover_single(
|
||||||
|
host, username=username, password=password, timeout=8
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _run(action):
|
||||||
|
device = await _device()
|
||||||
|
await device.update()
|
||||||
|
if action == "on":
|
||||||
|
await device.turn_on()
|
||||||
|
elif action == "off":
|
||||||
|
await device.turn_off()
|
||||||
|
elif action != "status":
|
||||||
|
raise ValueError("Accion no soportada")
|
||||||
|
await device.update()
|
||||||
|
state = "encendido" if device.is_on else "apagado"
|
||||||
|
return f"termo: {state}"
|
||||||
|
|
||||||
|
|
||||||
|
def controlar_termo(action):
|
||||||
|
"""Ejecuta una accion Tapo y devuelve un mensaje seguro para Telegram."""
|
||||||
|
try:
|
||||||
|
return asyncio.run(_run(action))
|
||||||
|
except Exception as exc:
|
||||||
|
message = str(exc)
|
||||||
|
if "TAPO_" in message:
|
||||||
|
return f"Error de configuracion Tapo: {message}"
|
||||||
|
return f"No se pudo controlar el termo: {message[:180]}"
|
||||||
|
|
||||||
|
|
||||||
|
async def _discover():
|
||||||
|
devices = await Discover.discover(timeout=5)
|
||||||
|
for device in devices.values():
|
||||||
|
print(f"{type(device).__name__} host={device.host!r} mac={device.mac}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(_discover())
|
||||||
Reference in New Issue
Block a user