221 lines
7.8 KiB
Python
221 lines
7.8 KiB
Python
"""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: <b>{fallo}</b>"
|
|
return (f"🤖 Dreame D10 Plus: <b>{texto_estado}</b>\n"
|
|
f"🔋 Bateria: <b>{d.get('battery_level')}%</b> ({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"📊 <b>Estadísticas Dreame</b>\n"
|
|
f"Limpiezas totales: <b>{d.get('total_clean_times')}</b>\n"
|
|
f"Tiempo total: <b>{d.get('total_clean_time')} min</b>\n"
|
|
f"Area total: <b>{d.get('total_clean_area')} m2</b>")
|
|
|
|
|
|
def consumibles():
|
|
vac = conectar()
|
|
d = vac.status().data
|
|
return (f"🔧 <b>Consumibles</b>\n"
|
|
f"Cepillo: <b>{d.get('brush_life_level')}%</b> "
|
|
f"({d.get('brush_left_time')}h restantes)\n"
|
|
f"Lateral: <b>{d.get('brush_life_level2')}%</b> "
|
|
f"({d.get('brush_left_time2')}h restantes)\n"
|
|
f"Filtro: <b>{d.get('filter_life_level')}%</b> "
|
|
f"({d.get('filter_left_time')}h restantes)")
|
|
|
|
|
|
def auto_estado():
|
|
vac = conectar()
|
|
d = vac.status().data
|
|
return ("🗑️ Vaciado automático: "
|
|
f"<b>{'ON' if d.get('auto_collect') else 'OFF'}</b>")
|
|
|
|
|
|
def voz_estado():
|
|
vac = conectar()
|
|
d = vac.status().data
|
|
return (f"🔊 Voz actual: <b>{d.get('voice_id')}</b> | "
|
|
f"Volumen: <b>{d.get('volume')}</b>")
|
|
|
|
|
|
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("<b>", "").replace("</b>", ""))
|
|
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("<b>", "").replace("</b>", ""))
|
|
elif cmd == "stats":
|
|
print(stats().replace("<b>", "").replace("</b>", ""))
|
|
elif cmd == "consumibles":
|
|
print(consumibles().replace("<b>", "").replace("</b>", ""))
|
|
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("<b>", "").replace("</b>", ""))
|
|
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))
|