diff --git a/vigilancia/aspiradora.py b/vigilancia/aspiradora.py
new file mode 100644
index 0000000..43f090b
--- /dev/null
+++ b/vigilancia/aspiradora.py
@@ -0,0 +1,101 @@
+"""Aspiradora Dreame D10 Plus (dreame.vacuum.r2205) via python-miio.
+
+El modelo r2205 habla el spec MIoT de mc1808, asi que se le pasa el
+mapping de mc1808 explicitamente. 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)
+
+ESTADOS = {
+ 1: "limpiando",
+ 2: "inactiva",
+ 3: "pausada",
+ 4: "error",
+ 5: "volviendo a la base",
+ 6: "cargando",
+ 7: "actualizando",
+}
+
+
+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")
+ return DreameVacuum(ip, token, model="dreame.vacuum.r2205",
+ mapping=MIOT_MAPPING["dreame.vacuum.mc1808"])
+
+
+def estado():
+ vac = conectar()
+ st = vac.status()
+ cod = st.device_status
+ texto_estado = ESTADOS.get(cod, f"codigo {cod}")
+ return (f"🤖 Dreame D10 Plus: {texto_estado}\n"
+ f"🔋 Bateria: {st.battery_level}% "
+ f"({'cargando' if st.charging_state else 'no cargando'})\n"
+ f"🧹 Modo: {st.cleaning_mode} | "
+ f"Tiempo: {st.cleaning_time} | Area: {st.cleaning_area}")
+
+
+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.pause()
+ print("Pausada.")
+ elif cmd == "locate":
+ vac.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))
diff --git a/vigilancia/pi_bot.py b/vigilancia/pi_bot.py
index e33cd3e..39ee162 100644
--- a/vigilancia/pi_bot.py
+++ b/vigilancia/pi_bot.py
@@ -754,6 +754,8 @@ def procesar(texto, chat_id):
elif t.startswith("bajar ") or t.startswith("/bajar "):
n = _int_arg(texto)
procesar_bajar(n, chat_id)
+ elif comando == "aspiradora":
+ procesar_aspiradora(argumento, chat_id)
elif comando in ("menu", "menú", "inicio", "start") and not argumento:
try:
from menu_idd import enviar_menu
@@ -1003,6 +1005,45 @@ def procesar_novedades(chat_id):
log(f"NOVEDADES: {len(items)} items")
+def procesar_aspiradora(argumento, chat_id):
+ """Comandos de la Dreame D10 Plus: estado|start|stop|home|pause|locate."""
+ accion = (argumento or "estado").lower().strip()
+ if accion not in ("estado", "start", "stop", "home", "pause", "locate"):
+ enviar("Uso: /aspiradora estado|start|stop|home|pause|locate.",
+ chat_id)
+ return
+ try:
+ import aspiradora
+ except Exception as e:
+ log(f"aspiradora import error: {e}")
+ enviar("❌ No se pudo cargar el modulo aspiradora", chat_id)
+ return
+ try:
+ if accion == "estado":
+ enviar(aspiradora.estado(), chat_id)
+ else:
+ vac = aspiradora.conectar()
+ if accion == "start":
+ vac.start()
+ enviar("🧹 Limpieza iniciada.", chat_id)
+ elif accion == "stop":
+ vac.stop()
+ enviar("⏹️ Limpieza detenida.", chat_id)
+ elif accion == "home":
+ vac.home()
+ enviar("🏠 Volviendo a la base.", chat_id)
+ elif accion == "pause":
+ vac.pause()
+ enviar("⏸️ Pausada.", chat_id)
+ elif accion == "locate":
+ vac.play_sound()
+ enviar("🔊 Emitiendo sonido de localización.", chat_id)
+ log(f"ASPIRADORA {accion} ok")
+ except Exception as e:
+ log(f"aspiradora error: {e}")
+ enviar(f"❌ Error con la aspiradora: {e}", chat_id)
+
+
def _int_arg(texto):
"""Extrae el numero de un comando tipo '/bajar 3'."""
partes = texto.split()