"""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))