"""Aspiradora Dreame D10 Plus (dreame.vacuum.r2205) via python-miio. Usa el spec MIoT REAL del r2205 (NO el de mc1808: ahi la bateria esta en otro siid y por eso marcaba 2%). Lee IP/token del .env del directorio. Uso: python aspiradora.py estado|start|stop|home|pause|locate """ import os import sys BASE = os.path.dirname(os.path.abspath(__file__)) if BASE not in sys.path: sys.path.insert(0, BASE) # Spec https://home.miot-spec.com/spec/dreame.vacuum.r2205 MAP_R2205 = { "battery_level": {"siid": 3, "piid": 1}, "charging_state": {"siid": 3, "piid": 2}, "device_fault": {"siid": 2, "piid": 2}, "device_status": {"siid": 2, "piid": 1}, "cleaning_mode": {"siid": 4, "piid": 4}, "cleaning_time": {"siid": 4, "piid": 2}, "cleaning_area": {"siid": 4, "piid": 3}, "volume": {"siid": 7, "piid": 1}, "start_clean": {"siid": 2, "aiid": 1}, "stop_clean": {"siid": 4, "aiid": 2}, "pause": {"siid": 2, "aiid": 2}, "home": {"siid": 3, "aiid": 1}, "locate": {"siid": 7, "aiid": 1}, "play_sound": {"siid": 7, "aiid": 2}, } ESTADOS = { 1: "limpiando", 2: "inactiva", 3: "pausada", 4: "error", 5: "volviendo a la base", 6: "cargando", 7: "fregando", 11: "creando mapa", 12: "limpieza de zona", 13: "carga completa", 14: "actualizando", } CARGAS = {1: "cargando", 2: "no cargando", 5: "volviendo a cargar"} MODOS = {0: "silencioso", 1: "estándar", 2: "medio", 3: "fuerte"} def _env(nombre, defecto=""): valor = os.getenv(nombre, defecto).strip() if not valor: try: with open(os.path.join(BASE, ".env")) as f: for linea in f: linea = linea.strip() if linea and not linea.startswith("#") and "=" in linea: k, v = linea.split("=", 1) if k.strip() == nombre: valor = v.strip() break except OSError: pass return valor def conectar(): from miio.integrations.vacuum.dreame.dreamevacuum_miot import ( MIOT_MAPPING, DreameVacuum, ) ip = _env("DREAME_IP", "192.168.50.122") token = _env("DREAME_TOKEN") if not token: raise RuntimeError("Falta DREAME_TOKEN en el .env") # El mapping se resuelve por modelo: se registra el r2205 (spec real). MIOT_MAPPING.setdefault("dreame.vacuum.r2205", MAP_R2205) return DreameVacuum(ip, token, model="dreame.vacuum.r2205") def estado(): vac = conectar() d = vac.status().data cod = d.get("device_status") texto_estado = ESTADOS.get(cod, f"codigo {cod}") carga = CARGAS.get(d.get("charging_state"), f"codigo {d.get('charging_state')}") modo = MODOS.get(d.get("cleaning_mode"), d.get("cleaning_mode")) fallo = d.get("device_fault") extra = "" if fallo in (0, None) else f"\n⚠️ Fallo: {fallo}" return (f"🤖 Dreame D10 Plus: {texto_estado}\n" f"🔋 Bateria: {d.get('battery_level')}% ({carga})\n" f"🧹 Modo: {modo} | " f"Tiempo: {d.get('cleaning_time')} min | " f"Area: {d.get('cleaning_area')} m2{extra}") def main(argv): if len(argv) < 2: print("Uso: aspiradora.py estado|start|stop|home|pause|locate") return 1 cmd = argv[1].lower() try: vac = conectar() if cmd == "estado": print(estado().replace("", "").replace("", "")) elif cmd == "start": vac.start() print("Limpieza iniciada.") elif cmd == "stop": vac.stop() print("Limpieza detenida.") elif cmd == "home": vac.home() print("Volviendo a la base.") elif cmd == "pause": vac.call_action("pause") print("Pausada.") elif cmd == "locate": vac.call_action("play_sound") print("Emitiendo sonido de localizacion.") else: print("Uso: aspiradora.py estado|start|stop|home|pause|locate") return 1 except Exception as e: print(f"ERROR: {e}") return 2 return 0 if __name__ == "__main__": sys.exit(main(sys.argv))