"""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}, "voice_id": {"siid": 7, "piid": 2}, "set_voice": {"siid": 7, "aiid": 4}, "auto_collect": {"siid": 15, "piid": 1}, "start_collect": {"siid": 15, "aiid": 1}, "first_clean_time": {"siid": 12, "piid": 1}, "total_clean_time": {"siid": 12, "piid": 2}, "total_clean_times": {"siid": 12, "piid": 3}, "total_clean_area": {"siid": 12, "piid": 4}, "brush_left_time": {"siid": 9, "piid": 1}, "brush_life_level": {"siid": 9, "piid": 2}, "brush_left_time2": {"siid": 10, "piid": 1}, "brush_life_level2": {"siid": 10, "piid": 2}, "filter_life_level": {"siid": 11, "piid": 1}, "filter_left_time": {"siid": 11, "piid": 2}, "mop_mode": {"siid": 4, "piid": 5}, "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"} AGUAS = {1: "bajo", 2: "medio", 3: "alto"} 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 _get(vac, nombre): d = vac.status().data return d.get(nombre) def stats(): vac = conectar() d = vac.status().data return (f"📊 Estadísticas Dreame\n" f"Limpiezas totales: {d.get('total_clean_times')}\n" f"Tiempo total: {d.get('total_clean_time')} min\n" f"Area total: {d.get('total_clean_area')} m2") def consumibles(): vac = conectar() d = vac.status().data return (f"🔧 Consumibles\n" f"Cepillo: {d.get('brush_life_level')}% " f"({d.get('brush_left_time')}h restantes)\n" f"Lateral: {d.get('brush_life_level2')}% " f"({d.get('brush_left_time2')}h restantes)\n" f"Filtro: {d.get('filter_life_level')}% " f"({d.get('filter_left_time')}h restantes)") def auto_estado(): vac = conectar() d = vac.status().data return ("🗑️ Vaciado automático: " f"{'ON' if d.get('auto_collect') else 'OFF'}") def voz_estado(): vac = conectar() d = vac.status().data return (f"🔊 Voz actual: {d.get('voice_id')} | " f"Volumen: {d.get('volume')}") def main(argv): if len(argv) < 2: print("Uso: aspiradora.py estado|start|stop|home|pause|locate|" "vaciar|auto|stats|consumibles|potencia|agua|voz") return 1 cmd = argv[1].lower() arg = argv[2] if len(argv) > 2 else "" 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.") elif cmd == "vaciar": vac.call_action("start_collect") print("Vaciando en la base.") elif cmd == "auto": if arg.lower() in ("on", "1"): vac.set_property_by(15, 1, 1) print("Vaciado automatico activado.") elif arg.lower() in ("off", "0"): vac.set_property_by(15, 1, 0) print("Vaciado automatico desactivado.") else: print(auto_estado().replace("", "").replace("", "")) elif cmd == "stats": print(stats().replace("", "").replace("", "")) elif cmd == "consumibles": print(consumibles().replace("", "").replace("", "")) elif cmd == "potencia": if arg.isdigit() and int(arg) in MODOS: vac.set_property_by(4, 4, int(arg)) print(f"Potencia: {MODOS[int(arg)]}.") else: d = vac.status().data print(f"Potencia actual: {MODOS.get(d.get('cleaning_mode'))} " f"(0-3).") elif cmd == "agua": if arg.isdigit() and int(arg) in AGUAS: vac.set_property_by(4, 5, int(arg)) print(f"Agua: {AGUAS[int(arg)]}.") else: d = vac.status().data print(f"Agua actual: {AGUAS.get(d.get('mop_mode'))} (1-3).") elif cmd == "voz": if arg: vac.set_voice(arg) print(f"Voz cambiada a {arg} (si el pack existe).") else: print(voz_estado().replace("", "").replace("", "")) else: print("Uso: aspiradora.py estado|start|stop|home|pause|locate|" "vaciar|auto|stats|consumibles|potencia|agua|voz") return 1 except Exception as e: print(f"ERROR: {e}") return 2 return 0 if __name__ == "__main__": sys.exit(main(sys.argv))