diff --git a/vigilancia/menu_idd.py b/vigilancia/menu_idd.py
index 4b94136..8d8eb8c 100644
--- a/vigilancia/menu_idd.py
+++ b/vigilancia/menu_idd.py
@@ -118,6 +118,7 @@ def teclado_domotica():
],
[
_btn("π₯οΈ Pantalla", PREFIJO + "cat:pantalla"),
+ _btn("ποΈ Mesillas", PREFIJO + "cat:mesillas"),
],
FILA_VOLVER,
])
@@ -181,6 +182,21 @@ def teclado_pantalla():
])
+def teclado_mesillas():
+ return _mk([
+ [_btn("π Estado mesillas", PREFIJO + "acc:mesilla:estado")],
+ [
+ _btn("ποΈ Juanjo ON", PREFIJO + "acc:mesilla:juanjo:on"),
+ _btn("ποΈ Juanjo OFF", PREFIJO + "acc:mesilla:juanjo:off"),
+ ],
+ [
+ _btn("ποΈ Nahid ON", PREFIJO + "acc:mesilla:nahid:on"),
+ _btn("ποΈ Nahid OFF", PREFIJO + "acc:mesilla:nahid:off"),
+ ],
+ [_btn("β¬
οΈ DomΓ³tica", PREFIJO + "cat:domotica")],
+ ])
+
+
def teclado_tv():
return _mk([
[
@@ -424,6 +440,7 @@ def _header(categoria):
"potencia": "β‘ Potencia aspiradora",
"termo": "π Termo",
"pantalla": "π₯οΈ Pantalla",
+ "mesillas": "ποΈ Mesillas",
"sistema": "π οΈ Sistema",
"router": "π‘ Router ASUS",
}
@@ -469,6 +486,7 @@ def _teclado_categoria(chat_id, categoria):
"potencia": teclado_potencia,
"termo": teclado_termo,
"pantalla": teclado_pantalla,
+ "mesillas": teclado_mesillas,
"sistema": teclado_sistema,
"router": teclado_router,
}
@@ -685,6 +703,17 @@ def _ejecutar_accion(chat_id, message_id, partes):
"bloquear/desbloquear:",
teclado_dispositivos(lista))
return
+ if accion == "mesilla":
+ # sub: estado | nombre | nombre:accion
+ rest = ":".join(partes[1:])
+ trozos = rest.split(":")
+ if len(trozos) == 1 and trozos[0] == "estado":
+ _reabrir(chat_id, message_id, "mesillas", "meross estado")
+ elif len(trozos) == 2 and trozos[1] in ("on", "off"):
+ pi_bot.procesar(f"meross {trozos[0]} {trozos[1]}", chat_id)
+ _cerrar_menu(chat_id, message_id)
+ _fin_accion(chat_id, "mesillas")
+ return
if accion == "tablets":
if sub in ("bloquear", "desbloquear"):
verbo = "desactivar" if sub == "bloquear" else "activar"
diff --git a/vigilancia/meross_control.py b/vigilancia/meross_control.py
new file mode 100644
index 0000000..c598299
--- /dev/null
+++ b/vigilancia/meross_control.py
@@ -0,0 +1,114 @@
+"""Enchufes Meross (mesillas) via cuenta Meross en la nube.
+
+Lee MEROSS_EMAIL/PASSWORD del .env. Solo lectura salvo on/off explicito.
+Uso: python meross_control.py estado|on|off [nombre]
+"""
+import asyncio
+import os
+import ssl
+import sys
+
+BASE = os.path.dirname(os.path.abspath(__file__))
+if BASE not in sys.path:
+ sys.path.insert(0, BASE)
+
+
+def _env(nombre):
+ valor = os.getenv(nombre, "").strip()
+ if not valor:
+ try:
+ with open(os.path.join(BASE, ".env")) as f:
+ for linea in f:
+ linea = linea.strip()
+ if linea.startswith(nombre + "="):
+ valor = linea.split("=", 1)[1].strip()
+ break
+ except OSError:
+ pass
+ return valor
+
+
+async def _dispositivos():
+ from meross_iot.http_api import MerossHttpClient
+ from meross_iot.manager import MerossManager
+ http = await MerossHttpClient.async_from_user_password(
+ api_base_url="https://iotx-eu.meross.com",
+ email=_env("MEROSS_EMAIL"), password=_env("MEROSS_PASSWORD"),
+ ssl_context=ssl.create_default_context())
+ mgr = MerossManager(http_client=http)
+ await mgr.async_init()
+ await mgr.async_device_discovery()
+ return mgr, http
+
+
+def _estado_dev(d):
+ try:
+ v = getattr(d, "is_on", None)
+ if callable(v):
+ v = v()
+ if isinstance(v, bool):
+ return v
+ except Exception:
+ pass
+ return None
+
+
+async def _actuar(nombre, accion):
+ mgr, http = await _dispositivos()
+ try:
+ objetivo = (nombre or "").lower()
+ elegidos = [d for d in mgr.find_devices()
+ if not objetivo or objetivo in (d.name or "").lower()]
+ if not elegidos:
+ return [f"β No encuentro '{nombre}' (solo mesillas)."]
+ lineas = []
+ for d in elegidos:
+ try:
+ await d.async_update()
+ except Exception:
+ pass
+ for d in elegidos:
+ if accion == "on":
+ try:
+ await d.async_turn_on()
+ except Exception as e:
+ lineas.append(f"β {d.name}: {str(e)[:100]}")
+ continue
+ elif accion == "off":
+ try:
+ await d.async_turn_off()
+ except Exception as e:
+ lineas.append(f"β {d.name}: {str(e)[:100]}")
+ continue
+ try:
+ await d.async_update()
+ except Exception:
+ pass
+ on = _estado_dev(d)
+ txt = "encendido" if on else ("apagado" if on is False else "?")
+ lineas.append(f"ποΈ {d.name}: {txt}")
+ return lineas
+ finally:
+ mgr.close()
+ await http.async_logout()
+
+
+def controlar(nombre, accion):
+ """Devuelve lista de lineas HTML con el resultado."""
+ try:
+ return asyncio.run(_actuar(nombre or "", accion or "estado"))
+ except Exception as e:
+ return [f"β Error Meross: {str(e)[:150]}"]
+
+
+def main(argv):
+ if len(argv) < 2 or argv[1] not in ("estado", "on", "off"):
+ print("Uso: meross_control.py estado|on|off [nombre]")
+ return 1
+ for linea in controlar(argv[2] if len(argv) > 2 else "", argv[1]):
+ print(linea.replace("", "").replace("", ""))
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py
index 1af8e1b..c845084 100644
--- a/vigilancia/pi_bot.py
+++ b/vigilancia/pi_bot.py
@@ -530,6 +530,25 @@ def procesar(texto, chat_id):
except Exception as e:
log(f"{comando} error: {e}")
enviar(f"β Error controlando {comando}: {e}", chat_id)
+ elif comando == "meross":
+ try:
+ from meross_control import controlar as meross_controlar
+ tok = argumento.lower().strip().split()
+ if not tok:
+ nombre, accion = "", "estado"
+ elif tok[0] in ("estado", "on", "off") and len(tok) == 1:
+ nombre, accion = "", tok[0]
+ else:
+ nombre = tok[0]
+ accion = tok[1] if len(tok) > 1 else "estado"
+ if accion not in ("estado", "on", "off"):
+ enviar("Uso: /meross [juanjo|nahid] estado|on|off.", chat_id)
+ else:
+ for linea in meross_controlar(nombre, accion):
+ enviar(linea, chat_id)
+ except Exception as e:
+ log(f"meross error: {e}")
+ enviar(f"β Error Meross: {e}", chat_id)
elif comando == "router":
partes_router = argumento.lower().split()
accion_router = partes_router[0] if partes_router else "ayuda"
@@ -811,7 +830,9 @@ def procesar(texto, chat_id):
"10. termo on|off|estado β controla el enchufe Tapo\n"
" βοΈ Ej: /termo estado\n"
"10c. pantalla on|off|estado β enchufe de la pantalla\n"
- " βοΈ Ej: /pantalla estado\n\n"
+ " βοΈ Ej: /pantalla estado\n"
+ "10d. meross [juanjo|nahid] estado|on|off β enchufes mesillas\n"
+ " βοΈ Ej: /meross estado\n\n"
"βββ π DOMΓTICA βββ\n"
"10b. aspiradora ... β Dreame D10 Plus\n"
" βοΈ Ej: /aspiradora estado, /aspiradora vaciar, /aspiradora stats, /aspiradora potencia 3, /aspiradora agua 2, /aspiradora voz\n\n"