Ankündigung

Einklappen
Keine Ankündigung bisher.

How-To: Xiaomi Vacuum S10+/X10+ Integration in SmartHomeNG (c102gl)

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    How-To: Xiaomi Vacuum S10+/X10+ Integration in SmartHomeNG (c102gl)

    Ich habe oben genannten Roboter und nach einigem Rumtüfteln die Einbindung in smarthome hinbekommen. Damit sich andere den Kopf nicht zerbrechen müssen ( bzw. ich nach der nächsten komplett Zerlegung meines Systems nicht wieder bei 0 anfange) habe ich ein kleines HowTo verfasst.

    Startpunkt: Das xiaomi Plugin funktioniert mit dieser Generation nicht mehr. Es muss das MIOT Protokoll verwendet werden. Aktuell nur in einer DEV Version verfügpar.
    Bash
    # direkt vom Master-Branch:
    pip3 install git+https://github.com/rytilahti/python-miio.git
    Zusätzlich brauchst du natürlich die IP und den eindeutigen 32-stelligen Token. Hierzu gibt es genügend Anleitungen im Netz.

    Was ich auf die harte Tour rausfinden musste ist dass der Roboter sämtliche im Startbefehl mitgegebnen Rauminformationen ignoriert. Ihr müsst erst die Einstellungen für einen Raum definieren und dann den Roboter dorthin schicken. Die Raum ID's starten bei 1. Kann man durch probieren rausbekommen oder auch durch eine spezielle Version der Xiaomi Home App (venv Version) die in der Lage ist die Befehle als Logdateien auszugeben.

    Alternativ nutzt ihr das Skript hier und verändert Werte in einzelnen Räumen. Ihr könnt dann auf der App sehen welcher Raum sich verändert hat. Damit sind die ID's ermittelt.

    #!/usr/bin/env python3
    import json
    from miio import MiotDevice

    IP = '192.168.X.X'
    TOKEN = 'DEIN_TOKEN_HIER'

    # TEST-ID: Hier 1, 2, 3... durchprobieren
    TEST_ID = 5

    # Wir senden: Wasser Hoch (3), Saugen Turbo (4), Modus Mix (2)
    # Wenn sich der Raum in der App ändert, hast du die ID gefunden.
    CONFIG = [[TEST_ID, 3, 4, 0, 2]]

    dev = MiotDevice(ip=IP, token=TOKEN)
    print(f"Setze ID {TEST_ID} auf TURBO...")
    # siid:6 (Config), aiid:2 (Set)
    dev.send("action", {'siid': 6, 'aiid': 2, 'in': [{'piid': 4, 'value': json.dumps({"customeClean": CONFIG})}]})
    Dann habe ich mir eine items.yaml gebaut (natürlich für jeden Raum)

    HTML-Code:
    sauger:
        start_mission:
            type: bool
            visu_acl: rw
            # Trigger für die Logik
            enforce_updates: 'true'
    
        rooms:
            kitchen:
                select:
                    type: bool
                    visu_acl: rw
                fan:
                    type: num
                    visu_acl: rw
                    # 0=Aus, 1=Leicht, 2=Standard, 3=Stark, 4=Turbo
                water:
                    type: num
                    visu_acl: rw
                    # 0=Aus, 1=Niedrig, 2=Mittel, 3=Hoch
    ​
    Die Logic funktioniert nach 3 Regeln:

    ​1. Safe Values: Es sendet niemals 0 für Wasser oder Saugen (der Roboter mag das nicht). Stattdessen wird 1 als Platzhalter gesendet, wenn eine Funktion aus ist.
    2. Modus-Steuerung: Die eigentliche Funktion (Nur Saugen vs. Wischen) wird über den Modus-Parameter (letzte Ziffer) gesteuert.
    3. Ablauf: Erst Konfiguration senden (siid:6) -> Warten -> Startbefehl senden (siid:4).

    HTML-Code:
    #!/usr/bin/env python3
    import logging
    import json
    import time
    
    logger = logging.getLogger(__name__)
    
    # Import Fallback für verschiedene miio Versionen
    try: from miio import MiotDevice
    except ImportError:
        try: from miio.miot_device import MiotDevice
        except ImportError: from miio.device import Device as MiotDevice
    
    # === KONFIGURATION ===
    IP = '192.168.X.X'
    TOKEN = 'dein Token'
    
    # ID MAPPING (Muss zur Karte passen!)
    ROOM_MAPPING = {
        'kueche':     1,
        'bad':        2,
        ...
    }
    
    try:
        trigger_source = trigger['source']
        trigger_value = trigger['value']
      
        # === START MISSION ===
        if trigger_source == 'sauger.start_mission' and trigger_value == True:
            dev = MiotDevice(ip=IP, token=TOKEN)
            logger.info("### LOGIK START: Zonenreinigung (Safe Params) ###")
          
            config_list = []      # Payload für siid:6 (Einstellungen)
            start_list_ids = []   # Payload für siid:4 (Start)
          
            for name, rid in ROOM_MAPPING.items():
                if hasattr(sh.sauger.rooms, name):
                    room_item = getattr(sh.sauger.rooms, name)
                  
                    # Nur selektierte Räume beachten (Item muss True sein!)
                    if room_item.select():
                        # 1. Rohwerte aus Visu (0-4)
                        raw_fan = int(room_item.fan()) if room_item.fan() is not None else 2
                        raw_water = int(room_item.water()) if room_item.water() is not None else 1
                      
                        # 2. Werte bereinigen ("Safe Values") & Modus bestimmen
                        # Der Roboter akzeptiert keine 0 bei Fan/Water.
                        # Wir senden min. 1 und steuern "Aus" über den Modus.
                      
                        safe_fan = raw_fan if raw_fan > 0 else 1
                        safe_water = raw_water if raw_water > 0 else 1
                        mode = 2 # Default: Mix
                      
                        # --- MODUS LOGIK ---
                        if raw_water == 0:
                            # Wasser Aus -> Modus 0 (Nur Saugen)
                            mode = 0
                            # Bei Modus 0 (Nur Saugen) erwartet der Roboter oft
                            # auch an der Wasser-Stelle (Index 1) den Fan-Wert oder ignoriert ihn.
                            # Wir lassen safe_water auf 1 oder raw_fan, sicher ist sicher.
                          
                        elif raw_fan == 0:
                            # Saugen Aus -> Modus 1 (Nur Wischen)
                            mode = 1
                      
                        else:
                            # Beides An -> Modus 2 (Mix)
                            mode = 2
    
                        logger.info(f" + Raum {name} (ID {rid}): Mode={mode} | Sende: Water={safe_water}, Fan={safe_fan}")
                      
                        # 3. Das Protokoll-Array bauen
                        # Struktur: [ID, WASSER, FAN, 0, MODUS]
                        config_list.append([rid, safe_water, safe_fan, 0, mode])
                      
                        # Start-Payload (Dummy Werte, wichtig ist nur die ID)
                        start_list_ids.append([rid, 1, 1, 2, 1])
    
            if config_list:
                # SCHRITT A: Konfiguration in den Roboter schreiben (siid:6)
                try:
                    json_config = json.dumps({"customeClean": config_list})
                    dev.send("action", {'siid': 6, 'aiid': 2, 'in': [{'piid': 4, 'value': json_config}]})
                except Exception as e:
                    logger.error(f"Fehler Konfig: {e}")
    
                # WICHTIG: Pause, damit der Roboter speichert
                time.sleep(1)
    
                # SCHRITT B: Reinigung starten (siid:4)
                try:
                    json_start = json.dumps({"selects": start_list_ids})
                    dev.send("action", {
                        'siid': 4, 'aiid': 1,
                        'in': [{'piid': 1, 'value': 18}, {'piid': 10, 'value': json_start}]
                    })
                    logger.info("Startbefehl gesendet.")
                except Exception as e:
                    logger.error(f"Fehler Start: {e}")
    
                # Reset Start-Button
                time.sleep(1)
                sh.sauger.start_mission(False)
              
            else:
                logger.warning("Keine Räume ausgewählt! (Prüfe select-Items)")
                sh.sauger.start_mission(False)
    
        # === BASIS BEFEHLE (Start/Pause/Home) ===
        elif trigger_source == 'sauger.command':
            dev = MiotDevice(ip=IP, token=TOKEN)
            cmd = str(sh.sauger.command())
            if cmd == 'start': dev.send("action", {'siid': 2, 'aiid': 1})
            elif cmd == 'pause': dev.send("action", {'siid': 2, 'aiid': 2})
            elif cmd == 'home': dev.send("action", {'siid': 3, 'aiid': 1})
    
    except Exception as e:
        logger.error(f"CRASH: {e}")
    ​
    Falls du später mal Logs debuggen musst, hier ist der Schlüssel zum Protokoll:
    Der Konfigurations-String (customeClean):
    Das Format ist [ID, A, B, C, D].
    Index Bedeutung Werte
    0 Raum ID 1-99 (Kartenabhängig)
    1 Wasser 1 (Niedrig) - 3 (Hoch). Keine 0 senden!
    2 Saugen 1 (Leise) - 4 (Turbo). Keine 0 senden!
    3 Dummy Immer 0
    4 Modus 0 = Nur Saugen/1 = Nur Wischen/2 = Saugen & Wischen

    Wichtigste Erkenntnis:
    Der Roboter ignoriert die Parameter im Startbefehl (selects). Man muss zwingend erst customeClean senden, kurz warten, und dann starten.

    Sollte es Fragen geben oder etwas fehlen gerne melden.



Lädt...
X