diff --git a/docs/raspberry_pi.md b/docs/raspberry_pi.md index a9588b0..1f4a751 100644 --- a/docs/raspberry_pi.md +++ b/docs/raspberry_pi.md @@ -150,6 +150,8 @@ Reservas aplicadas para los 39 leases actuales y 3 reservas historicas conservad Control parental por dispositivo o grupo: horarios, bloqueo temporal, perfiles, pausas desde Telegram y registro de cambios. Se implementara despues de terminar la normalizacion de nombres del inventario. +La lectura avanzada de control parental usa `asusrouter` y requiere `ASUS_USERNAME` y `ASUS_PASSWORD` solo en el `.env` local de la Raspberry. No se guardan en el repositorio. + ## Despliegue La Pi no tiene Git operativo para este flujo. El despliegue se hace por el VPS y el tunel inverso: diff --git a/vigilancia/.env.example b/vigilancia/.env.example index 7643793..dfc9be8 100644 --- a/vigilancia/.env.example +++ b/vigilancia/.env.example @@ -13,6 +13,11 @@ TAPO_USERNAME= TAPO_PASSWORD= TAPO_TERMO_IP= +# ASUSWRT API para control parental (no compartir la contrasena) +ASUS_ROUTER_IP=192.168.50.1 +ASUS_USERNAME= +ASUS_PASSWORD= + # Franja horaria activa (formato 24h) HORA_INICIO=08 HORA_FIN=17 diff --git a/vigilancia/asus_api_control.py b/vigilancia/asus_api_control.py new file mode 100644 index 0000000..fff22ab --- /dev/null +++ b/vigilancia/asus_api_control.py @@ -0,0 +1,57 @@ +"""Lectura segura de datos ASUSWRT mediante la API web del router.""" + +import asyncio +import json +import os + +import aiohttp +from asusrouter import AsusData, AsusRouter + + +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, default=""): + return os.getenv(name, FILE_ENV.get(name, default)).strip() + + +async def _parental_data(): + username = _env("ASUS_USERNAME") + password = _env("ASUS_PASSWORD") + if not username or not password: + raise RuntimeError( + "Falta ASUS_USERNAME o ASUS_PASSWORD en el .env de la Raspberry" + ) + session = aiohttp.ClientSession() + router = AsusRouter( + hostname=_env("ASUS_ROUTER_IP", "192.168.50.1"), + username=username, + password=password, + session=session, + ) + try: + await router.async_connect() + return await router.async_get_data(AsusData.PARENTAL_CONTROL, force=True) + finally: + await router.async_disconnect() + await session.close() + + +def parental_status(): + data = asyncio.run(_parental_data()) + return json.dumps(data, ensure_ascii=False, indent=2, default=str) diff --git a/vigilancia/router_control.py b/vigilancia/router_control.py index 2f7796d..a123a9a 100644 --- a/vigilancia/router_control.py +++ b/vigilancia/router_control.py @@ -115,6 +115,30 @@ def reservas(): return f"
{html.escape(output)}
" +def control_parental(): + try: + from asus_api_control import parental_status + return f"
{html.escape(parental_status())}
" + except RuntimeError as exc: + return f"
{html.escape(str(exc))}
" + except Exception: + pass + keys = ( + "yadns_enable_x", + "yadns_mode", + "yadns_clientlist", + "bwdpi_enable", + "bwdpi_rulelist", + "keyword_rulelist", + "url_rulelist", + "time_zone", + ) + lines = [] + for key in keys: + lines.append(f"{key}={_run(f'nvram get {key}')}") + return f"
{html.escape(chr(10).join(lines))}
" + + def reiniciar(): return _run("reboot") @@ -128,5 +152,6 @@ if __name__ == "__main__": "inventario": inventario, "reservar": reservar_dhcp, "reservas": reservas, + "parental": control_parental, } print(actions.get(action, estado)())