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