Skip to content
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Roborock S5 "cleaning area" per Tastendruck?

NEWS

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    8.4k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.0k

  • Neues Video "KI im Smart Home" - ioBroker plus n8n
    BluefoxB
    Bluefox
    15
    1
    2.5k

Roborock S5 "cleaning area" per Tastendruck?

Geplant Angeheftet Gesperrt Verschoben Skripten / Logik
javascript
57 Beiträge 9 Kommentatoren 10.3k Aufrufe 15 Watching
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • AlCalzoneA AlCalzone

    @kaiserm Das ist die obere linke Ecke des Rechtecks. Liegt die ggf. hinter dem Mülleimer oder in einer Wand? Dann kann er da nicht hinfahren. Du brauchst die Koordinaten, wo der Sauger stehen würde, wenn er vor dem Mülleimer anhält.

    K Offline
    K Offline
    kaiserm
    schrieb am zuletzt editiert von kaiserm
    #23

    @AlCalzone Ich habe mir in der erstellten Karte das kleinstmögliche Rechteck in flolevac aufgezogen und diese Koordinaten übernommen. Der S50 fährt nicht mal ansatzweise in diese Richtung sondern irrt irgendwie nur um den Startpunkt herum.

    AlCalzoneA 1 Antwort Letzte Antwort
    0
    • K kaiserm

      @AlCalzone Ich habe mir in der erstellten Karte das kleinstmögliche Rechteck in flolevac aufgezogen und diese Koordinaten übernommen. Der S50 fährt nicht mal ansatzweise in diese Richtung sondern irrt irgendwie nur um den Startpunkt herum.

      AlCalzoneA Offline
      AlCalzoneA Offline
      AlCalzone
      Developer
      schrieb am zuletzt editiert von AlCalzone
      #24

      @kaiserm Kenn er denn die komplette Karte? Ist der gewünschte Punkt etwa 7 Meter rechts und 3 Meter oben vom Dock aus? Kannst du ggf. mit der App nachvollziehen, wo er hin will, ob das stimmt und warum er so dumm fährt? War ggf. eine Tür zu beim Erstellen der Karte?

      Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

      K 1 Antwort Letzte Antwort
      0
      • AlCalzoneA AlCalzone

        @kaiserm Kenn er denn die komplette Karte? Ist der gewünschte Punkt etwa 7 Meter rechts und 3 Meter oben vom Dock aus? Kannst du ggf. mit der App nachvollziehen, wo er hin will, ob das stimmt und warum er so dumm fährt? War ggf. eine Tür zu beim Erstellen der Karte?

        K Offline
        K Offline
        kaiserm
        schrieb am zuletzt editiert von
        #25

        @AlCalzone Ganz komisch.
        Soeben nochmal getestet. Nun fuhr er brav bis vor den Mülleimer.

        Keine Ahnung was da schiefgelaufen ist.

        LG Martin

        1 Antwort Letzte Antwort
        0
        • 0 0018

          @Brati sagte in Roborock S5 "cleaning area" per Tastendruck?:

          Du musst ihn einmal alles reinigen lassen und einstellen, dass die Karte gespeichert wird.

          Hi Brati,

          wo genau kann ich sagen das die Karte gespeichert wird? Hast du evtl einen Screenshot dazu? Behält er dann auch die Karte wenn man den Sauger über den Button am Sauger startet?

          B Offline
          B Offline
          Brati
          schrieb am zuletzt editiert von
          #26

          @0018

          Einstellungen/ Staubsaugereinstellungen/ Kartenspeichermodus on/off

          Ich habe aber die russische App, sollte in der Chinaversion jedoch genauso sein. Das geht erst, wenn einmal die Karte aufgebaut wurde. Danach bleibt sie immer so erhalten.

          Grüße

          Brati

          CCU2 - 46 Geräte, ioBroker auf Intel NUC (DN2820FYKH) mit Tab als Frontend, Projekt Gartenhaus mit HM

          K 0 2 Antworten Letzte Antwort
          0
          • B Brati

            @0018

            Einstellungen/ Staubsaugereinstellungen/ Kartenspeichermodus on/off

            Ich habe aber die russische App, sollte in der Chinaversion jedoch genauso sein. Das geht erst, wenn einmal die Karte aufgebaut wurde. Danach bleibt sie immer so erhalten.

            Grüße

            Brati

            K Offline
            K Offline
            kaiserm
            schrieb am zuletzt editiert von
            #27

            @Brati Ja, das habe ich bei mir so eingestellt.

            Ihm fehlt lediglich noch ein Raum wo er noch nicht war.

            Da lasse ich das nächste Mal die Türe offen.

            1 Antwort Letzte Antwort
            0
            • K kaiserm

              @Stoni Genau das hätte ich auch gerne.

              1. Alles per Sprachbefehle.
                Also z.B. Alexa, sauge das Esszimmer oder Alexa, sauge das Wohnzimmer
                Falls alles gereinigt werden soll: Alexa, sauge die Wohnung.

              Für die Reinigung / Wartung wäre es ideal wenn ich Alexa sagen könnte: Alexa, fahre Hugo (so heisst unser S50) zum Mülleimer.

              Wie macht man das ?
              Ok, die Zonen der Zimmer oder Bereiche, also Wohnzimmer, Esszimmer, Mülleimer,...) bekommt man ja in der Flolevac App.

              Muss man für die komplette Wohnung die Zone über alles ziehen?

              Was macht man dann mit den ermittelten Zonen?
              Wo müssen die eingebunden werden damit alles per Sprache läuft?

              Wenn er vor dem Mülleimer steht und gereinigt wurde müsste man ihn ja mit Alexa, fahre Hugo zur Ladestation zurückschicken können?

              Weitere Befehle: Alexa, pausiere Saugvorgang oder Alexa, beende Saugvorgang.

              Was gibt man da wo an ?

              Man kann ja im Alexa2 Adapter in der Summary immer den letzten Befehl sehen, also kann man darauf triggern und eine Szene abspielen lassen?

              Danke im Voraus.

              LG Martin

              S Offline
              S Offline
              Stoni
              schrieb am zuletzt editiert von
              #28

              @kaiserm sagte in Roborock S5 "cleaning area" per Tastendruck?:

              @Stoni Genau das hätte ich auch gerne.

              1. Alles per Sprachbefehle.
                Also z.B. Alexa, sauge das Esszimmer oder Alexa, sauge das Wohnzimmer
                Falls alles gereinigt werden soll: Alexa, sauge die Wohnung.

              Für die Reinigung / Wartung wäre es ideal wenn ich Alexa sagen könnte: Alexa, fahre Hugo (so heisst unser S50) zum Mülleimer.

              Wie macht man das ?
              Ok, die Zonen der Zimmer oder Bereiche, also Wohnzimmer, Esszimmer, Mülleimer,...) bekommt man ja in der Flolevac App.

              Muss man für die komplette Wohnung die Zone über alles ziehen?

              Was macht man dann mit den ermittelten Zonen?
              Wo müssen die eingebunden werden damit alles per Sprache läuft?

              Wenn er vor dem Mülleimer steht und gereinigt wurde müsste man ihn ja mit Alexa, fahre Hugo zur Ladestation zurückschicken können?

              Weitere Befehle: Alexa, pausiere Saugvorgang oder Alexa, beende Saugvorgang.

              Was gibt man da wo an ?

              Man kann ja im Alexa2 Adapter in der Summary immer den letzten Befehl sehen, also kann man darauf triggern und eine Szene abspielen lassen?

              Danke im Voraus.

              LG Martin

              Einfach nach der Anleitung aus dem Roboter Forum vorgehen. Dort ist alles im Detail beschrieben.

              Ich habe in der Alexa App Routinen erstellt wo ich selber sage "Alexa, mach das Haus sauber" und dann eine Szene, die ich per Cloud Adapter als "Gerät" angelegt habe, gestartet.

              Funzt bis heute einwandfrei. Das geht natürlich auch mit Teilbereichen wie Esszimmer, Wohnzimmer oder Flur.
              Wichtig ist, den Robo nur noch auf diese Weise los zu schicken und nicht mehr einfach reinigen zu lassen. Sonst baut er die Karte neu auf und die Koordinaten stimmen ggf. nicht mehr.

              K 1 Antwort Letzte Antwort
              1
              • S Stoni

                @kaiserm sagte in Roborock S5 "cleaning area" per Tastendruck?:

                @Stoni Genau das hätte ich auch gerne.

                1. Alles per Sprachbefehle.
                  Also z.B. Alexa, sauge das Esszimmer oder Alexa, sauge das Wohnzimmer
                  Falls alles gereinigt werden soll: Alexa, sauge die Wohnung.

                Für die Reinigung / Wartung wäre es ideal wenn ich Alexa sagen könnte: Alexa, fahre Hugo (so heisst unser S50) zum Mülleimer.

                Wie macht man das ?
                Ok, die Zonen der Zimmer oder Bereiche, also Wohnzimmer, Esszimmer, Mülleimer,...) bekommt man ja in der Flolevac App.

                Muss man für die komplette Wohnung die Zone über alles ziehen?

                Was macht man dann mit den ermittelten Zonen?
                Wo müssen die eingebunden werden damit alles per Sprache läuft?

                Wenn er vor dem Mülleimer steht und gereinigt wurde müsste man ihn ja mit Alexa, fahre Hugo zur Ladestation zurückschicken können?

                Weitere Befehle: Alexa, pausiere Saugvorgang oder Alexa, beende Saugvorgang.

                Was gibt man da wo an ?

                Man kann ja im Alexa2 Adapter in der Summary immer den letzten Befehl sehen, also kann man darauf triggern und eine Szene abspielen lassen?

                Danke im Voraus.

                LG Martin

                Einfach nach der Anleitung aus dem Roboter Forum vorgehen. Dort ist alles im Detail beschrieben.

                Ich habe in der Alexa App Routinen erstellt wo ich selber sage "Alexa, mach das Haus sauber" und dann eine Szene, die ich per Cloud Adapter als "Gerät" angelegt habe, gestartet.

                Funzt bis heute einwandfrei. Das geht natürlich auch mit Teilbereichen wie Esszimmer, Wohnzimmer oder Flur.
                Wichtig ist, den Robo nur noch auf diese Weise los zu schicken und nicht mehr einfach reinigen zu lassen. Sonst baut er die Karte neu auf und die Koordinaten stimmen ggf. nicht mehr.

                K Offline
                K Offline
                kaiserm
                schrieb am zuletzt editiert von
                #29

                @Stoni Die Karte ist gespeichert, sollte also nicht mehr neu aufegabut werden.

                Ich will von den Szenen weg.

                Ich realisiere wohl alles direkt mit Blockly.

                LG Martin

                1 Antwort Letzte Antwort
                0
                • K Offline
                  K Offline
                  kaiserm
                  schrieb am zuletzt editiert von
                  #30

                  Ich bin begeistert.

                  Unser S50 ist nun voll per Sprache bedienbar.

                  Er fährt zum Mülleimer, reinigt alles oder auch nur bestimmte Räume, macht ein Päuschen, fährt zurück zur Ladestation.

                  iobroker rockt.

                  LG Martin

                  1 Antwort Letzte Antwort
                  0
                  • B Brati

                    @0018

                    Einstellungen/ Staubsaugereinstellungen/ Kartenspeichermodus on/off

                    Ich habe aber die russische App, sollte in der Chinaversion jedoch genauso sein. Das geht erst, wenn einmal die Karte aufgebaut wurde. Danach bleibt sie immer so erhalten.

                    Grüße

                    Brati

                    0 Offline
                    0 Offline
                    0018
                    schrieb am zuletzt editiert von
                    #31

                    @Brati sagte in Roborock S5 "cleaning area" per Tastendruck?:

                    Einstellungen/ Staubsaugereinstellungen/ Kartenspeichermodus on/off

                    Ich habe aber die russische App, sollte in der Chinaversion jedoch genauso sein. Das geht erst, wenn einmal die Karte aufgebaut wurde. Danach bleibt sie immer so erhalten.

                    Meinst du MiHome App oder Flole VAC?
                    Habe bei mir Valetudo drauf, geht das dann trotzdem?

                    Mfg
                    0018

                    AlCalzoneA 1 Antwort Letzte Antwort
                    0
                    • 0 0018

                      @Brati sagte in Roborock S5 "cleaning area" per Tastendruck?:

                      Einstellungen/ Staubsaugereinstellungen/ Kartenspeichermodus on/off

                      Ich habe aber die russische App, sollte in der Chinaversion jedoch genauso sein. Das geht erst, wenn einmal die Karte aufgebaut wurde. Danach bleibt sie immer so erhalten.

                      Meinst du MiHome App oder Flole VAC?
                      Habe bei mir Valetudo drauf, geht das dann trotzdem?

                      AlCalzoneA Offline
                      AlCalzoneA Offline
                      AlCalzone
                      Developer
                      schrieb am zuletzt editiert von
                      #32

                      @0018 Wenn du Valetudo hast, is die Karte egal. Mein Skript oben erkennt die Orientierung und dreht die Koordinaten entsprechend.

                      Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                      0 1 Antwort Letzte Antwort
                      0
                      • AlCalzoneA AlCalzone

                        @0018 Wenn du Valetudo hast, is die Karte egal. Mein Skript oben erkennt die Orientierung und dreht die Koordinaten entsprechend.

                        0 Offline
                        0 Offline
                        0018
                        schrieb am zuletzt editiert von
                        #33

                        @AlCalzone Mal angenommen ich habe den Sauger durch meine Wohnung geschickt und habe dann eine komplette Karte aus der ich die Koordinaten für die Bereichsreinigung ziehen kann. Was ist denn wenn ich den Sauger dann mal per Knopfdruck am Gerät starten will? Dann generiert er doch eine neue Karte und die Bereichkoordinaten sind hinüber oder? Kann man das vermeiden?

                        Mfg
                        0018

                        AlCalzoneA 1 Antwort Letzte Antwort
                        0
                        • 0 0018

                          @AlCalzone Mal angenommen ich habe den Sauger durch meine Wohnung geschickt und habe dann eine komplette Karte aus der ich die Koordinaten für die Bereichsreinigung ziehen kann. Was ist denn wenn ich den Sauger dann mal per Knopfdruck am Gerät starten will? Dann generiert er doch eine neue Karte und die Bereichkoordinaten sind hinüber oder? Kann man das vermeiden?

                          AlCalzoneA Offline
                          AlCalzoneA Offline
                          AlCalzone
                          Developer
                          schrieb am zuletzt editiert von
                          #34

                          @0018 Nein, die Koordinaten stimmen immer noch.

                          Die Karte dreht sich beim Sauger der 1. Generation gerne, aber mein Skript erkennt dank Valetudo automatisch die Rotation und rechnet die Koordinaten entsprechend um.

                          Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                          0 1 Antwort Letzte Antwort
                          0
                          • AlCalzoneA AlCalzone

                            @0018 Nein, die Koordinaten stimmen immer noch.

                            Die Karte dreht sich beim Sauger der 1. Generation gerne, aber mein Skript erkennt dank Valetudo automatisch die Rotation und rechnet die Koordinaten entsprechend um.

                            0 Offline
                            0 Offline
                            0018
                            schrieb am zuletzt editiert von
                            #35

                            @AlCalzone Ist es egal wie die Karte steht, wenn ich meine Koordinaten für die Raumzuweisung definiere?

                            Mfg
                            0018

                            AlCalzoneA 1 Antwort Letzte Antwort
                            0
                            • 0 0018

                              @AlCalzone Ist es egal wie die Karte steht, wenn ich meine Koordinaten für die Raumzuweisung definiere?

                              AlCalzoneA Offline
                              AlCalzoneA Offline
                              AlCalzone
                              Developer
                              schrieb am zuletzt editiert von
                              #36

                              @0018
                              Siehe Skript:

                              // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                              // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                              

                              Winkel 0 heißt "Robo links vom Dock". Wenn das bei dir anders ist, müsstest du in Zeile 119ff die Interpretation der Winkel ändern. Beispiel: Sauger steht bei dir normalerweise rechts, dann 180 => 0, 270 => 90, 90 => 270, 0 => 180.

                              Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                              0 1 Antwort Letzte Antwort
                              0
                              • K kaiserm

                                @AlCalzone Vielen Dank.

                                Ich probiere gerade noch was anderes:

                                Alexa reagiert und spricht den Text.
                                Egal wie ich die Koordinaten eingebe. Der S50 macht keinen Mucks.
                                GFebe ich nur die ersten beiden Punkte ohne Klammern aber mit Komma an fährt er aus der Ladestation, irrt etwas umher und sagt dann er kann nicht zum Ziel fahren.

                                Man müsste doch in den GoToPoint Datenpunkt direkt die Koordinate reinschreiben können?!

                                Habe alles probiert. Mit Klammern, ohne ... mit Kommas

                                Gebe ich ihm nur die ersten beiden Zahlen fährt er aus der Docking Station irrt kurz umher und sagt dann er kann nicht an die Koordinate fahren.

                                Im Log finde ich immer nur: GoTo only work with two arguments seperated by

                                LG Martin

                                fb33cc15-1813-4d1b-bb7d-36794475ee4d-grafik.png

                                M Offline
                                M Offline
                                Mor9oth
                                schrieb am zuletzt editiert von
                                #37

                                @kaiserm Hey! Vielen Dank für das Blocky! Finde ich eine tolle Funktion! Habe ich gestern auch mal direkt nachgebaut, allerdings funktioniert es bei mir leider nicht. :-( Jetzt hoffe ich hier auf etwas Hilfe ...
                                Im Protokoll kommt die Meldung:

                                09:35:03.244	info	javascript.0 (1571) Start javascript script.js.common.Leere_den_Staubbehälter
                                09:35:03.247	info	javascript.0 (1571) script.js.common.Leere_den_Staubbehälter: registered 1 subscription and 0 schedules
                                

                                Der Roborock fährt weder los noch gibt die Alexa den Speak-Befehl aus. Allerdings gibt Sie als Antwort auf meine Spracheingabe die Bestätigung "ok" bzw. den "Pieps" aus.

                                Das Blocky scheint mir identisch nachgebaut zu sein. Aus folgenden Pfaden kommen die Variabeln:

                                • summary: Alexa2/0/History/summary

                                • speak: Alexa2/0/Echo-Devices/Geräte-NUMMER/Commands/speak

                                • go to Point: Mihome-vacuum/0/control/goto

                                • Die Koordianten kommen aus Flolevac (Vgl. @AlCalzone )

                                Screenshot Blocky mit Protokoll:
                                blocky-staubbehälter-leeren.jpg

                                Oder muss ich noch etwas ganz anderes machen, bzw. haben damit das Script überhaupt funktioniert? Bin leider ein Noob, daher komme ich leider nicht weiter ...

                                Was ich auch noch probiert habe: Ich habe dem Script einen "smartName" gegeben, damit dieses im Cloud-Adapter als "Smart Gerät" auftaucht und dann in den Alexa-Routinen per Sprachsteuerung integrierbar wird. Auch um synonyme Befehle definieren zu können. Ist das richtig, bzw. notwendig mit dem Blocky-Script? Immerhin ist doch der speak-Befehl eine Bedingung im Script ...

                                Könnte mir bitte jemand helfen?

                                1 Antwort Letzte Antwort
                                0
                                • AlCalzoneA AlCalzone

                                  @kaiserm Also wie versprochen:

                                  Hier die Variante, die bei mir im Einsatz ist (inklusive Test der Kartenorientierung via Valetudo). Muss als TypeScript angelegt werden und benötigt das zusätzliche Paket "axios":

                                  import axios from "axios";
                                  
                                  // Hier den Hostnamen und Zugangsdaten eintragen, unter dem Valetudo erreichbar ist
                                  const roboHostname = "rockrobo.fritz.box";
                                  const valetudoAuth = {
                                  	username: 'valetudo-username',
                                  	password: 'valetudo-password'
                                  }
                                  // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                  const idVacuumState = "mihome-vacuum.0.info.state";
                                  
                                  const center: Point = [25600, 25600];
                                  // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                  // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                  const rooms: Record<string, Rectangle[]> = {
                                      "Küche": [[21600, 29100, 23500, 33100]],
                                      "Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                      "Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                      "Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                      "Bad": [[21500, 27500, 23200, 29000]],
                                  };
                                  
                                  // ===============================================================================
                                  
                                  type Rectangle = [number, number, number, number];
                                  type Point = [number, number];
                                  
                                  /** rotates a rectangle by 90° around the absolute origin */
                                  function rotate90([x1, y1, x2, y2]: Rectangle): Rectangle {
                                      return [-y2, x1, -y1, x2];
                                  }
                                  
                                  /** rotates a rectangle by 180° around the absolute origin */
                                  function rotate180([x1, y1, x2, y2]: Rectangle): Rectangle {
                                      return [-x2, -y2, -x1, -y1];
                                  }
                                  
                                  /** Rotates a rectangle around the given center by the given angle */
                                  function rotate(rect: Rectangle, center: Point, angle: number) {
                                      rect = [rect[0] - center[0], rect[1] - center[1], rect[2] - center[0], rect[3] - center[1]];
                                      if (angle % 180 === 90) {
                                          rect = rotate90(rect);
                                          angle -= 90;
                                      }
                                      if (angle === 180) {
                                          rect = rotate180(rect);
                                      }
                                      rect = [rect[0] + center[0], rect[1] + center[1], rect[2] + center[0], rect[3] + center[1]];
                                      return rect;
                                  }
                                  
                                  for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                      createState(`Staubsauger.${room}`, {
                                          type: "boolean",
                                          read: true,
                                          write: true,
                                          role: "switch",
                                          name: `${room} saugen`,
                                      });
                                      on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                          if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                  
                                          if (getMapRotation() === -1 /* unbekannt */) {
                                              // We need to test the map rotation
                                              const rotation = await testMapRotation();
                                              log(`Die Karte ist ${rotation !== 0 ? `um ${rotation}° ` : "nicht "}rotiert.`);
                                              await rememberMapRotation(rotation);
                                          }
                                  
                                          // Now that we know how the map is rotated, we can clean the room
                                          cleanRoom(room);
                                      });
                                  }
                                  
                                  createState(`Staubsauger.stop`, {
                                      type: "boolean",
                                      read: true,
                                      write: true,
                                      role: "switch",
                                      name: `Staubsauger anhalten`,
                                  });
                                  on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                      stopCleanup();
                                  });
                                  
                                  const idMapRotated = "Staubsauger.info.mapRotated";
                                  createState(idMapRotated, {
                                      type: "number",
                                      read: true,
                                      write: false,
                                      role: "indicator",
                                      states: {
                                          "-1": "unknown",
                                          "0": "Robo links vom Dock",
                                          "90": "Robo unten vom Dock",
                                          "180": "Robo rechts vom Dock",
                                          "270": "Robo oben vom Dock",
                                      },
                                      name: `Wie die Karte rotiert ist`,
                                  });
                                  
                                  /** 
                                   * Bestimmt die Kartenrotation wenn der Staubsauger neben dem Dock steht
                                   * 0 Grad bedeutet, der Sauger steht links
                                   */
                                  async function testMapRotation(): Promise<number> {
                                      log("Teste Kartenorientierung...");
                                      const { data: { charger, robot } } = await axios({
                                          url: `http://${roboHostname}/api/map/latest`,
                                          auth: valetudoAuth
                                      });
                                  
                                      // Valetudo zählt von oben links nach unten rechts, d.h. die Y-Koordinaten
                                      // sind entgegengesetzt der mathematischen Definition
                                      let c2r = [robot[0] - charger[0], charger[1] - robot[1]];
                                      const angle = Math.atan2(c2r[1], c2r[0]) * 180 / Math.PI;
                                      if (angle <= 45 && angle >= -45) {
                                          // Sauger steht rechts
                                          return 180;
                                      } else if (angle > 45 && angle < 135) {
                                          // Sauger steht oben
                                          return 270;
                                      } else if (angle < -45 && angle > -135) {
                                          // Sauger steht unten
                                          return 90;
                                      } else {
                                          return 0;
                                      }
                                  }
                                  
                                  function rememberMapRotation(rotation: number): Promise<void> {
                                      return setStateAsync(idMapRotated, rotation);
                                  }
                                  function getMapRotation(): number {
                                      return getState(idMapRotated).val;
                                  }
                                  
                                  // "Forget" map rotation when the vacuum starts charging
                                  on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                      setState(idMapRotated, -1 /* unknown */);
                                      // And reset all control states
                                      for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                          setState(`Staubsauger.${room}`, false, true);
                                      }
                                      setState(`Staubsauger.stop`, false, true);
                                  });
                                  
                                  async function beginCleanup(): Promise<void> {
                                  }
                                  
                                  async function cancelCurrentAction(): Promise<void> {
                                      setState("mihome-vacuum.0.control.pause", true);
                                      // wait for the "paused" status before going home
                                      await waitForPauseOrSleep();
                                  }
                                  
                                  async function stopCleanup(): Promise<void> {
                                      log(`Saugvorgang abgebrochen!`);
                                      setState(`Staubsauger.stop`, true, true);
                                  
                                      if (getState(idVacuumState).val !== 8) {
                                          await cancelCurrentAction();
                                  
                                          await setStateAsync('mihome-vacuum.0.control.home', true);
                                          // wait for the "charging" status before resolving
                                          await waitFor(idVacuumState, 8);
                                      }
                                  
                                      log(`Staubsauger ist in der Basis`);
                                  
                                      setState(`Staubsauger.stop`, false, true);
                                  }
                                  
                                  async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                      log(`Saugvorgang für ${room} gestartet!`);
                                      setState(`Staubsauger.${room}`, true, true);
                                  
                                      const mapRotation = getMapRotation();
                                      const originalCoords = rooms[room];
                                      log(`original coordinates: ${JSON.stringify(originalCoords)}`);
                                      const roomCoords = rooms[room].map(rect => rotate(rect, center, mapRotation));
                                      log(`rotated coordinates: ${JSON.stringify(roomCoords)}`);
                                  
                                      const coords = roomCoords[0];
                                      const targetCoords = [
                                          ((coords[0] + coords[2]) / 2).toFixed(0),
                                          ((coords[1] + coords[3]) / 2).toFixed(0),
                                      ]
                                  
                                      // go to center of first zone
                                      const gotoString = targetCoords.join(",");
                                      await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                      log(`Fahre zur Mitte von ${room}`);
                                      await wait(10000);
                                      await waitForPauseOrSleep();
                                  
                                      if (getState("Staubsauger.stop").val) return;
                                  
                                      const zoneCleanString = roomCoords.map(zone => {
                                          return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                      }).join(",");
                                  
                                      log("Starte Zonenreinigung...");
                                      await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                      // wait for the cleanup to finish
                                      await waitFor(idVacuumState, 6);
                                  }
                                  
                                  async function waitForPauseOrSleep(): Promise<void> {
                                      log("Warte auf Zustand schlafen oder Pause...");
                                      switch (getState(idVacuumState).val) {
                                          case 3:
                                          case 10:
                                              log(" => Zustand bereits aktiv!");
                                              return;
                                          default:
                                              await Promise.race([
                                                  waitFor(idVacuumState, 10),
                                                  waitFor(idVacuumState, 3),
                                              ]);
                                              log(" => Zustand erreicht!");
                                      }
                                  }
                                  
                                  function wait(ms: number): Promise<void> {
                                      return new Promise(resolve => {
                                          setTimeout(resolve, ms);
                                      });
                                  }
                                  
                                  function waitFor(stateID: string, value: any): Promise<void> {
                                      return new Promise(resolve => {
                                          const handler = (obj: iobJS.ChangedStateObject) => {
                                              if (obj.newState.val === value) {
                                                  unsubscribe(handler);
                                                  resolve();
                                              }
                                          }
                                          subscribe(stateID, handler);
                                      })
                                  }
                                  
                                  function setStateAsync(id: string, state: any): Promise<void> {
                                      return new Promise(res => {
                                          setState(id, state, () => res());
                                      });
                                  }
                                  

                                  Wenn du die automatische Rotation nicht benötigst oder kein Valetudo hast, wäre folgendes die vereinfachte Variante:

                                  // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                  const idVacuumState = "mihome-vacuum.0.info.state";
                                  
                                  // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                  // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                  const rooms: Record<string, Rectangle[]> = {
                                  	"Küche": [[21600, 29100, 23500, 33100]],
                                  	"Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                  	"Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                  	"Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                  	"Bad": [[21500, 27500, 23200, 29000]],
                                  };
                                  
                                  // ===============================================================================
                                  
                                  type Rectangle = [number, number, number, number];
                                  
                                  for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                  	createState(`Staubsauger.${room}`, {
                                  		type: "boolean",
                                  		read: true,
                                  		write: true,
                                  		role: "switch",
                                  		name: `${room} saugen`,
                                  	});
                                  	on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                  		if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                  		
                                  		cleanRoom(room);
                                  	});
                                  }
                                  
                                  createState(`Staubsauger.stop`, {
                                  	type: "boolean",
                                  	read: true,
                                  	write: true,
                                  	role: "switch",
                                  	name: `Staubsauger anhalten`,
                                  });
                                  on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                  	stopCleanup();
                                  });
                                  
                                  // Reset all control states when the vacuum starts charging
                                  on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                  	for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                  		setState(`Staubsauger.${room}`, false, true);
                                  	}
                                  	setState(`Staubsauger.stop`, false, true);
                                  });
                                  
                                  async function cancelCurrentAction(): Promise<void> {
                                  	setState("mihome-vacuum.0.control.pause", true);
                                  	// wait for the "paused" status before going home
                                  	await waitForPauseOrSleep();
                                  }
                                  
                                  async function stopCleanup(): Promise<void> {
                                  	log(`Saugvorgang abgebrochen!`);
                                  	setState(`Staubsauger.stop`, true, true);
                                  
                                  	if (getState(idVacuumState).val !== 8) {
                                  		await cancelCurrentAction();
                                  
                                  		await setStateAsync('mihome-vacuum.0.control.home', true);
                                  		// wait for the "charging" status before resolving
                                  		await waitFor(idVacuumState, 8);
                                  	}
                                  
                                  	log(`Staubsauger ist in der Basis`);
                                  
                                  	setState(`Staubsauger.stop`, false, true);
                                  }
                                  
                                  async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                  	log(`Saugvorgang für ${room} gestartet!`);
                                  	setState(`Staubsauger.${room}`, true, true);
                                  
                                  	const roomCoords = rooms[room];
                                  
                                  	const coords = roomCoords[0];
                                  	const targetCoords = [
                                  		((coords[0] + coords[2]) / 2).toFixed(0),
                                  		((coords[1] + coords[3]) / 2).toFixed(0),
                                  	]
                                  
                                  	// go to center of first zone
                                  	const gotoString = targetCoords.join(",");
                                  	await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                  	log(`Fahre zur Mitte von ${room}`);
                                  	await wait(10000);
                                  	await waitForPauseOrSleep();
                                  
                                  	if (getState("Staubsauger.stop").val) return;
                                  
                                  	const zoneCleanString = roomCoords.map(zone => {
                                  		return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                  	}).join(",");
                                  
                                  	log("Starte Zonenreinigung...");
                                  	await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                  	// wait for the cleanup to finish
                                  	await waitFor(idVacuumState, 6);
                                  }
                                  
                                  async function waitForPauseOrSleep(): Promise<void> {
                                  	log("Warte auf Zustand schlafen oder Pause...");
                                  	switch (getState(idVacuumState).val) {
                                  		case 3:
                                  		case 10:
                                  			log(" => Zustand bereits aktiv!");
                                  			return;
                                  		default:
                                  			await Promise.race([
                                  				waitFor(idVacuumState, 10),
                                  				waitFor(idVacuumState, 3),
                                  			]);
                                  			log(" => Zustand erreicht!");
                                  	}
                                  }
                                  
                                  function wait(ms: number): Promise<void> {
                                  	return new Promise(resolve => {
                                  		setTimeout(resolve, ms);
                                  	});
                                  }
                                  
                                  function waitFor(stateID: string, value: any): Promise<void> {
                                  	return new Promise(resolve => {
                                  		const handler = (obj: iobJS.ChangedStateObject) => {
                                  			if (obj.newState.val === value) {
                                  				unsubscribe(handler);
                                  				resolve();
                                  			}
                                  		}
                                  		subscribe(stateID, handler);
                                  	})
                                  }
                                  
                                  function setStateAsync(id: string, state: any): Promise<void> {
                                  	return new Promise(res => {
                                  		setState(id, state, () => res());
                                  	});
                                  }
                                  

                                  Beide Varianten legen für jeden Raum und für Stop einen State an, den du dann jeweils mit einer Alexa-Routine ansprechen kannst.

                                  M Offline
                                  M Offline
                                  Mor9oth
                                  schrieb am zuletzt editiert von
                                  #38

                                  @AlCalzone Sauber! Das Script funktioniert nun auch bei mir hervorragend! Besten Dank! :blush: :clap:

                                  1 Antwort Letzte Antwort
                                  0
                                  • AlCalzoneA AlCalzone

                                    @kaiserm Also wie versprochen:

                                    Hier die Variante, die bei mir im Einsatz ist (inklusive Test der Kartenorientierung via Valetudo). Muss als TypeScript angelegt werden und benötigt das zusätzliche Paket "axios":

                                    import axios from "axios";
                                    
                                    // Hier den Hostnamen und Zugangsdaten eintragen, unter dem Valetudo erreichbar ist
                                    const roboHostname = "rockrobo.fritz.box";
                                    const valetudoAuth = {
                                    	username: 'valetudo-username',
                                    	password: 'valetudo-password'
                                    }
                                    // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                    const idVacuumState = "mihome-vacuum.0.info.state";
                                    
                                    const center: Point = [25600, 25600];
                                    // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                    // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                    const rooms: Record<string, Rectangle[]> = {
                                        "Küche": [[21600, 29100, 23500, 33100]],
                                        "Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                        "Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                        "Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                        "Bad": [[21500, 27500, 23200, 29000]],
                                    };
                                    
                                    // ===============================================================================
                                    
                                    type Rectangle = [number, number, number, number];
                                    type Point = [number, number];
                                    
                                    /** rotates a rectangle by 90° around the absolute origin */
                                    function rotate90([x1, y1, x2, y2]: Rectangle): Rectangle {
                                        return [-y2, x1, -y1, x2];
                                    }
                                    
                                    /** rotates a rectangle by 180° around the absolute origin */
                                    function rotate180([x1, y1, x2, y2]: Rectangle): Rectangle {
                                        return [-x2, -y2, -x1, -y1];
                                    }
                                    
                                    /** Rotates a rectangle around the given center by the given angle */
                                    function rotate(rect: Rectangle, center: Point, angle: number) {
                                        rect = [rect[0] - center[0], rect[1] - center[1], rect[2] - center[0], rect[3] - center[1]];
                                        if (angle % 180 === 90) {
                                            rect = rotate90(rect);
                                            angle -= 90;
                                        }
                                        if (angle === 180) {
                                            rect = rotate180(rect);
                                        }
                                        rect = [rect[0] + center[0], rect[1] + center[1], rect[2] + center[0], rect[3] + center[1]];
                                        return rect;
                                    }
                                    
                                    for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                        createState(`Staubsauger.${room}`, {
                                            type: "boolean",
                                            read: true,
                                            write: true,
                                            role: "switch",
                                            name: `${room} saugen`,
                                        });
                                        on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                            if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                    
                                            if (getMapRotation() === -1 /* unbekannt */) {
                                                // We need to test the map rotation
                                                const rotation = await testMapRotation();
                                                log(`Die Karte ist ${rotation !== 0 ? `um ${rotation}° ` : "nicht "}rotiert.`);
                                                await rememberMapRotation(rotation);
                                            }
                                    
                                            // Now that we know how the map is rotated, we can clean the room
                                            cleanRoom(room);
                                        });
                                    }
                                    
                                    createState(`Staubsauger.stop`, {
                                        type: "boolean",
                                        read: true,
                                        write: true,
                                        role: "switch",
                                        name: `Staubsauger anhalten`,
                                    });
                                    on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                        stopCleanup();
                                    });
                                    
                                    const idMapRotated = "Staubsauger.info.mapRotated";
                                    createState(idMapRotated, {
                                        type: "number",
                                        read: true,
                                        write: false,
                                        role: "indicator",
                                        states: {
                                            "-1": "unknown",
                                            "0": "Robo links vom Dock",
                                            "90": "Robo unten vom Dock",
                                            "180": "Robo rechts vom Dock",
                                            "270": "Robo oben vom Dock",
                                        },
                                        name: `Wie die Karte rotiert ist`,
                                    });
                                    
                                    /** 
                                     * Bestimmt die Kartenrotation wenn der Staubsauger neben dem Dock steht
                                     * 0 Grad bedeutet, der Sauger steht links
                                     */
                                    async function testMapRotation(): Promise<number> {
                                        log("Teste Kartenorientierung...");
                                        const { data: { charger, robot } } = await axios({
                                            url: `http://${roboHostname}/api/map/latest`,
                                            auth: valetudoAuth
                                        });
                                    
                                        // Valetudo zählt von oben links nach unten rechts, d.h. die Y-Koordinaten
                                        // sind entgegengesetzt der mathematischen Definition
                                        let c2r = [robot[0] - charger[0], charger[1] - robot[1]];
                                        const angle = Math.atan2(c2r[1], c2r[0]) * 180 / Math.PI;
                                        if (angle <= 45 && angle >= -45) {
                                            // Sauger steht rechts
                                            return 180;
                                        } else if (angle > 45 && angle < 135) {
                                            // Sauger steht oben
                                            return 270;
                                        } else if (angle < -45 && angle > -135) {
                                            // Sauger steht unten
                                            return 90;
                                        } else {
                                            return 0;
                                        }
                                    }
                                    
                                    function rememberMapRotation(rotation: number): Promise<void> {
                                        return setStateAsync(idMapRotated, rotation);
                                    }
                                    function getMapRotation(): number {
                                        return getState(idMapRotated).val;
                                    }
                                    
                                    // "Forget" map rotation when the vacuum starts charging
                                    on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                        setState(idMapRotated, -1 /* unknown */);
                                        // And reset all control states
                                        for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                            setState(`Staubsauger.${room}`, false, true);
                                        }
                                        setState(`Staubsauger.stop`, false, true);
                                    });
                                    
                                    async function beginCleanup(): Promise<void> {
                                    }
                                    
                                    async function cancelCurrentAction(): Promise<void> {
                                        setState("mihome-vacuum.0.control.pause", true);
                                        // wait for the "paused" status before going home
                                        await waitForPauseOrSleep();
                                    }
                                    
                                    async function stopCleanup(): Promise<void> {
                                        log(`Saugvorgang abgebrochen!`);
                                        setState(`Staubsauger.stop`, true, true);
                                    
                                        if (getState(idVacuumState).val !== 8) {
                                            await cancelCurrentAction();
                                    
                                            await setStateAsync('mihome-vacuum.0.control.home', true);
                                            // wait for the "charging" status before resolving
                                            await waitFor(idVacuumState, 8);
                                        }
                                    
                                        log(`Staubsauger ist in der Basis`);
                                    
                                        setState(`Staubsauger.stop`, false, true);
                                    }
                                    
                                    async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                        log(`Saugvorgang für ${room} gestartet!`);
                                        setState(`Staubsauger.${room}`, true, true);
                                    
                                        const mapRotation = getMapRotation();
                                        const originalCoords = rooms[room];
                                        log(`original coordinates: ${JSON.stringify(originalCoords)}`);
                                        const roomCoords = rooms[room].map(rect => rotate(rect, center, mapRotation));
                                        log(`rotated coordinates: ${JSON.stringify(roomCoords)}`);
                                    
                                        const coords = roomCoords[0];
                                        const targetCoords = [
                                            ((coords[0] + coords[2]) / 2).toFixed(0),
                                            ((coords[1] + coords[3]) / 2).toFixed(0),
                                        ]
                                    
                                        // go to center of first zone
                                        const gotoString = targetCoords.join(",");
                                        await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                        log(`Fahre zur Mitte von ${room}`);
                                        await wait(10000);
                                        await waitForPauseOrSleep();
                                    
                                        if (getState("Staubsauger.stop").val) return;
                                    
                                        const zoneCleanString = roomCoords.map(zone => {
                                            return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                        }).join(",");
                                    
                                        log("Starte Zonenreinigung...");
                                        await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                        // wait for the cleanup to finish
                                        await waitFor(idVacuumState, 6);
                                    }
                                    
                                    async function waitForPauseOrSleep(): Promise<void> {
                                        log("Warte auf Zustand schlafen oder Pause...");
                                        switch (getState(idVacuumState).val) {
                                            case 3:
                                            case 10:
                                                log(" => Zustand bereits aktiv!");
                                                return;
                                            default:
                                                await Promise.race([
                                                    waitFor(idVacuumState, 10),
                                                    waitFor(idVacuumState, 3),
                                                ]);
                                                log(" => Zustand erreicht!");
                                        }
                                    }
                                    
                                    function wait(ms: number): Promise<void> {
                                        return new Promise(resolve => {
                                            setTimeout(resolve, ms);
                                        });
                                    }
                                    
                                    function waitFor(stateID: string, value: any): Promise<void> {
                                        return new Promise(resolve => {
                                            const handler = (obj: iobJS.ChangedStateObject) => {
                                                if (obj.newState.val === value) {
                                                    unsubscribe(handler);
                                                    resolve();
                                                }
                                            }
                                            subscribe(stateID, handler);
                                        })
                                    }
                                    
                                    function setStateAsync(id: string, state: any): Promise<void> {
                                        return new Promise(res => {
                                            setState(id, state, () => res());
                                        });
                                    }
                                    

                                    Wenn du die automatische Rotation nicht benötigst oder kein Valetudo hast, wäre folgendes die vereinfachte Variante:

                                    // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                    const idVacuumState = "mihome-vacuum.0.info.state";
                                    
                                    // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                    // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                    const rooms: Record<string, Rectangle[]> = {
                                    	"Küche": [[21600, 29100, 23500, 33100]],
                                    	"Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                    	"Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                    	"Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                    	"Bad": [[21500, 27500, 23200, 29000]],
                                    };
                                    
                                    // ===============================================================================
                                    
                                    type Rectangle = [number, number, number, number];
                                    
                                    for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                    	createState(`Staubsauger.${room}`, {
                                    		type: "boolean",
                                    		read: true,
                                    		write: true,
                                    		role: "switch",
                                    		name: `${room} saugen`,
                                    	});
                                    	on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                    		if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                    		
                                    		cleanRoom(room);
                                    	});
                                    }
                                    
                                    createState(`Staubsauger.stop`, {
                                    	type: "boolean",
                                    	read: true,
                                    	write: true,
                                    	role: "switch",
                                    	name: `Staubsauger anhalten`,
                                    });
                                    on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                    	stopCleanup();
                                    });
                                    
                                    // Reset all control states when the vacuum starts charging
                                    on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                    	for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                    		setState(`Staubsauger.${room}`, false, true);
                                    	}
                                    	setState(`Staubsauger.stop`, false, true);
                                    });
                                    
                                    async function cancelCurrentAction(): Promise<void> {
                                    	setState("mihome-vacuum.0.control.pause", true);
                                    	// wait for the "paused" status before going home
                                    	await waitForPauseOrSleep();
                                    }
                                    
                                    async function stopCleanup(): Promise<void> {
                                    	log(`Saugvorgang abgebrochen!`);
                                    	setState(`Staubsauger.stop`, true, true);
                                    
                                    	if (getState(idVacuumState).val !== 8) {
                                    		await cancelCurrentAction();
                                    
                                    		await setStateAsync('mihome-vacuum.0.control.home', true);
                                    		// wait for the "charging" status before resolving
                                    		await waitFor(idVacuumState, 8);
                                    	}
                                    
                                    	log(`Staubsauger ist in der Basis`);
                                    
                                    	setState(`Staubsauger.stop`, false, true);
                                    }
                                    
                                    async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                    	log(`Saugvorgang für ${room} gestartet!`);
                                    	setState(`Staubsauger.${room}`, true, true);
                                    
                                    	const roomCoords = rooms[room];
                                    
                                    	const coords = roomCoords[0];
                                    	const targetCoords = [
                                    		((coords[0] + coords[2]) / 2).toFixed(0),
                                    		((coords[1] + coords[3]) / 2).toFixed(0),
                                    	]
                                    
                                    	// go to center of first zone
                                    	const gotoString = targetCoords.join(",");
                                    	await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                    	log(`Fahre zur Mitte von ${room}`);
                                    	await wait(10000);
                                    	await waitForPauseOrSleep();
                                    
                                    	if (getState("Staubsauger.stop").val) return;
                                    
                                    	const zoneCleanString = roomCoords.map(zone => {
                                    		return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                    	}).join(",");
                                    
                                    	log("Starte Zonenreinigung...");
                                    	await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                    	// wait for the cleanup to finish
                                    	await waitFor(idVacuumState, 6);
                                    }
                                    
                                    async function waitForPauseOrSleep(): Promise<void> {
                                    	log("Warte auf Zustand schlafen oder Pause...");
                                    	switch (getState(idVacuumState).val) {
                                    		case 3:
                                    		case 10:
                                    			log(" => Zustand bereits aktiv!");
                                    			return;
                                    		default:
                                    			await Promise.race([
                                    				waitFor(idVacuumState, 10),
                                    				waitFor(idVacuumState, 3),
                                    			]);
                                    			log(" => Zustand erreicht!");
                                    	}
                                    }
                                    
                                    function wait(ms: number): Promise<void> {
                                    	return new Promise(resolve => {
                                    		setTimeout(resolve, ms);
                                    	});
                                    }
                                    
                                    function waitFor(stateID: string, value: any): Promise<void> {
                                    	return new Promise(resolve => {
                                    		const handler = (obj: iobJS.ChangedStateObject) => {
                                    			if (obj.newState.val === value) {
                                    				unsubscribe(handler);
                                    				resolve();
                                    			}
                                    		}
                                    		subscribe(stateID, handler);
                                    	})
                                    }
                                    
                                    function setStateAsync(id: string, state: any): Promise<void> {
                                    	return new Promise(res => {
                                    		setState(id, state, () => res());
                                    	});
                                    }
                                    

                                    Beide Varianten legen für jeden Raum und für Stop einen State an, den du dann jeweils mit einer Alexa-Routine ansprechen kannst.

                                    0 Offline
                                    0 Offline
                                    0018
                                    schrieb am zuletzt editiert von 0018
                                    #39

                                    @AlCalzone sagte in Roborock S5 "cleaning area" per Tastendruck?:

                                    @kaiserm Also wie versprochen:

                                    Muss als TypeScript angelegt werden und benötigt das zusätzliche Paket "axios":

                                    Bekomme bei mir folgende Meldung:

                                    javascript.0	2019-12-07 00:24:41.947	warn	ERROR: Cannot find module 'axios'.
                                    javascript.0	2019-12-07 00:24:41.947	warn	^
                                    javascript.0	2019-12-07 00:24:41.947	warn	import axios from "axios";
                                    javascript.0	2019-12-07 00:24:41.947	warn	(2094) script.js.common.4_MiRobot.MiRobot_Zone_neu: TypeScript compilation had errors:
                                    

                                    Habe im JS Instanz das Paket axios hinzugefügt, aber die Meldung kommt trotzdem noch. Muss ich das noch irgendwo anders eintragen?

                                    Gibt es einen Grund warum der Sauger erst zu einem Spot fährt, bevor die Zonenreinigung startet?

                                    Mfg
                                    0018

                                    AlCalzoneA 1 Antwort Letzte Antwort
                                    0
                                    • 0 0018

                                      @AlCalzone sagte in Roborock S5 "cleaning area" per Tastendruck?:

                                      @kaiserm Also wie versprochen:

                                      Muss als TypeScript angelegt werden und benötigt das zusätzliche Paket "axios":

                                      Bekomme bei mir folgende Meldung:

                                      javascript.0	2019-12-07 00:24:41.947	warn	ERROR: Cannot find module 'axios'.
                                      javascript.0	2019-12-07 00:24:41.947	warn	^
                                      javascript.0	2019-12-07 00:24:41.947	warn	import axios from "axios";
                                      javascript.0	2019-12-07 00:24:41.947	warn	(2094) script.js.common.4_MiRobot.MiRobot_Zone_neu: TypeScript compilation had errors:
                                      

                                      Habe im JS Instanz das Paket axios hinzugefügt, aber die Meldung kommt trotzdem noch. Muss ich das noch irgendwo anders eintragen?

                                      Gibt es einen Grund warum der Sauger erst zu einem Spot fährt, bevor die Zonenreinigung startet?

                                      AlCalzoneA Offline
                                      AlCalzoneA Offline
                                      AlCalzone
                                      Developer
                                      schrieb am zuletzt editiert von
                                      #40

                                      @0018 sagte in Roborock S5 "cleaning area" per Tastendruck?:

                                      Habe im JS Instanz das Paket axios hinzugefügt, aber die Meldung kommt trotzdem noch. Muss ich das noch irgendwo anders eintragen?

                                      Ich bin mir nicht sicher, ob der aktuelle Adapter damit schon released ist. Wenn es die Option "Activate syntax help for these npm modules:" gibt, trag da auch noch axios ein. Ansonsten folgende Zeile über den Import schreiben:

                                      // @ts-ignore
                                      

                                      Gibt es einen Grund warum der Sauger erst zu einem Spot fährt, bevor die Zonenreinigung startet?

                                      Ja, das ist leiser als wenn er im Reinigungs-Modus in die Zone fährt.

                                      Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                                      1 Antwort Letzte Antwort
                                      0
                                      • AlCalzoneA AlCalzone

                                        @kaiserm Also wie versprochen:

                                        Hier die Variante, die bei mir im Einsatz ist (inklusive Test der Kartenorientierung via Valetudo). Muss als TypeScript angelegt werden und benötigt das zusätzliche Paket "axios":

                                        import axios from "axios";
                                        
                                        // Hier den Hostnamen und Zugangsdaten eintragen, unter dem Valetudo erreichbar ist
                                        const roboHostname = "rockrobo.fritz.box";
                                        const valetudoAuth = {
                                        	username: 'valetudo-username',
                                        	password: 'valetudo-password'
                                        }
                                        // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                        const idVacuumState = "mihome-vacuum.0.info.state";
                                        
                                        const center: Point = [25600, 25600];
                                        // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                        // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                        const rooms: Record<string, Rectangle[]> = {
                                            "Küche": [[21600, 29100, 23500, 33100]],
                                            "Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                            "Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                            "Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                            "Bad": [[21500, 27500, 23200, 29000]],
                                        };
                                        
                                        // ===============================================================================
                                        
                                        type Rectangle = [number, number, number, number];
                                        type Point = [number, number];
                                        
                                        /** rotates a rectangle by 90° around the absolute origin */
                                        function rotate90([x1, y1, x2, y2]: Rectangle): Rectangle {
                                            return [-y2, x1, -y1, x2];
                                        }
                                        
                                        /** rotates a rectangle by 180° around the absolute origin */
                                        function rotate180([x1, y1, x2, y2]: Rectangle): Rectangle {
                                            return [-x2, -y2, -x1, -y1];
                                        }
                                        
                                        /** Rotates a rectangle around the given center by the given angle */
                                        function rotate(rect: Rectangle, center: Point, angle: number) {
                                            rect = [rect[0] - center[0], rect[1] - center[1], rect[2] - center[0], rect[3] - center[1]];
                                            if (angle % 180 === 90) {
                                                rect = rotate90(rect);
                                                angle -= 90;
                                            }
                                            if (angle === 180) {
                                                rect = rotate180(rect);
                                            }
                                            rect = [rect[0] + center[0], rect[1] + center[1], rect[2] + center[0], rect[3] + center[1]];
                                            return rect;
                                        }
                                        
                                        for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                            createState(`Staubsauger.${room}`, {
                                                type: "boolean",
                                                read: true,
                                                write: true,
                                                role: "switch",
                                                name: `${room} saugen`,
                                            });
                                            on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                                if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                        
                                                if (getMapRotation() === -1 /* unbekannt */) {
                                                    // We need to test the map rotation
                                                    const rotation = await testMapRotation();
                                                    log(`Die Karte ist ${rotation !== 0 ? `um ${rotation}° ` : "nicht "}rotiert.`);
                                                    await rememberMapRotation(rotation);
                                                }
                                        
                                                // Now that we know how the map is rotated, we can clean the room
                                                cleanRoom(room);
                                            });
                                        }
                                        
                                        createState(`Staubsauger.stop`, {
                                            type: "boolean",
                                            read: true,
                                            write: true,
                                            role: "switch",
                                            name: `Staubsauger anhalten`,
                                        });
                                        on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                            stopCleanup();
                                        });
                                        
                                        const idMapRotated = "Staubsauger.info.mapRotated";
                                        createState(idMapRotated, {
                                            type: "number",
                                            read: true,
                                            write: false,
                                            role: "indicator",
                                            states: {
                                                "-1": "unknown",
                                                "0": "Robo links vom Dock",
                                                "90": "Robo unten vom Dock",
                                                "180": "Robo rechts vom Dock",
                                                "270": "Robo oben vom Dock",
                                            },
                                            name: `Wie die Karte rotiert ist`,
                                        });
                                        
                                        /** 
                                         * Bestimmt die Kartenrotation wenn der Staubsauger neben dem Dock steht
                                         * 0 Grad bedeutet, der Sauger steht links
                                         */
                                        async function testMapRotation(): Promise<number> {
                                            log("Teste Kartenorientierung...");
                                            const { data: { charger, robot } } = await axios({
                                                url: `http://${roboHostname}/api/map/latest`,
                                                auth: valetudoAuth
                                            });
                                        
                                            // Valetudo zählt von oben links nach unten rechts, d.h. die Y-Koordinaten
                                            // sind entgegengesetzt der mathematischen Definition
                                            let c2r = [robot[0] - charger[0], charger[1] - robot[1]];
                                            const angle = Math.atan2(c2r[1], c2r[0]) * 180 / Math.PI;
                                            if (angle <= 45 && angle >= -45) {
                                                // Sauger steht rechts
                                                return 180;
                                            } else if (angle > 45 && angle < 135) {
                                                // Sauger steht oben
                                                return 270;
                                            } else if (angle < -45 && angle > -135) {
                                                // Sauger steht unten
                                                return 90;
                                            } else {
                                                return 0;
                                            }
                                        }
                                        
                                        function rememberMapRotation(rotation: number): Promise<void> {
                                            return setStateAsync(idMapRotated, rotation);
                                        }
                                        function getMapRotation(): number {
                                            return getState(idMapRotated).val;
                                        }
                                        
                                        // "Forget" map rotation when the vacuum starts charging
                                        on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                            setState(idMapRotated, -1 /* unknown */);
                                            // And reset all control states
                                            for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                                setState(`Staubsauger.${room}`, false, true);
                                            }
                                            setState(`Staubsauger.stop`, false, true);
                                        });
                                        
                                        async function beginCleanup(): Promise<void> {
                                        }
                                        
                                        async function cancelCurrentAction(): Promise<void> {
                                            setState("mihome-vacuum.0.control.pause", true);
                                            // wait for the "paused" status before going home
                                            await waitForPauseOrSleep();
                                        }
                                        
                                        async function stopCleanup(): Promise<void> {
                                            log(`Saugvorgang abgebrochen!`);
                                            setState(`Staubsauger.stop`, true, true);
                                        
                                            if (getState(idVacuumState).val !== 8) {
                                                await cancelCurrentAction();
                                        
                                                await setStateAsync('mihome-vacuum.0.control.home', true);
                                                // wait for the "charging" status before resolving
                                                await waitFor(idVacuumState, 8);
                                            }
                                        
                                            log(`Staubsauger ist in der Basis`);
                                        
                                            setState(`Staubsauger.stop`, false, true);
                                        }
                                        
                                        async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                            log(`Saugvorgang für ${room} gestartet!`);
                                            setState(`Staubsauger.${room}`, true, true);
                                        
                                            const mapRotation = getMapRotation();
                                            const originalCoords = rooms[room];
                                            log(`original coordinates: ${JSON.stringify(originalCoords)}`);
                                            const roomCoords = rooms[room].map(rect => rotate(rect, center, mapRotation));
                                            log(`rotated coordinates: ${JSON.stringify(roomCoords)}`);
                                        
                                            const coords = roomCoords[0];
                                            const targetCoords = [
                                                ((coords[0] + coords[2]) / 2).toFixed(0),
                                                ((coords[1] + coords[3]) / 2).toFixed(0),
                                            ]
                                        
                                            // go to center of first zone
                                            const gotoString = targetCoords.join(",");
                                            await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                            log(`Fahre zur Mitte von ${room}`);
                                            await wait(10000);
                                            await waitForPauseOrSleep();
                                        
                                            if (getState("Staubsauger.stop").val) return;
                                        
                                            const zoneCleanString = roomCoords.map(zone => {
                                                return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                            }).join(",");
                                        
                                            log("Starte Zonenreinigung...");
                                            await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                            // wait for the cleanup to finish
                                            await waitFor(idVacuumState, 6);
                                        }
                                        
                                        async function waitForPauseOrSleep(): Promise<void> {
                                            log("Warte auf Zustand schlafen oder Pause...");
                                            switch (getState(idVacuumState).val) {
                                                case 3:
                                                case 10:
                                                    log(" => Zustand bereits aktiv!");
                                                    return;
                                                default:
                                                    await Promise.race([
                                                        waitFor(idVacuumState, 10),
                                                        waitFor(idVacuumState, 3),
                                                    ]);
                                                    log(" => Zustand erreicht!");
                                            }
                                        }
                                        
                                        function wait(ms: number): Promise<void> {
                                            return new Promise(resolve => {
                                                setTimeout(resolve, ms);
                                            });
                                        }
                                        
                                        function waitFor(stateID: string, value: any): Promise<void> {
                                            return new Promise(resolve => {
                                                const handler = (obj: iobJS.ChangedStateObject) => {
                                                    if (obj.newState.val === value) {
                                                        unsubscribe(handler);
                                                        resolve();
                                                    }
                                                }
                                                subscribe(stateID, handler);
                                            })
                                        }
                                        
                                        function setStateAsync(id: string, state: any): Promise<void> {
                                            return new Promise(res => {
                                                setState(id, state, () => res());
                                            });
                                        }
                                        

                                        Wenn du die automatische Rotation nicht benötigst oder kein Valetudo hast, wäre folgendes die vereinfachte Variante:

                                        // Hier den State eintragen, der den aktuellen Sauger-Status angibt
                                        const idVacuumState = "mihome-vacuum.0.info.state";
                                        
                                        // Koordiaten zählen von unten links (0,0) nach oben rechts (51200,51200)
                                        // Die folgenden Koordinaten gehen von nicht rotierter Karte (Winkel 0) aus
                                        const rooms: Record<string, Rectangle[]> = {
                                        	"Küche": [[21600, 29100, 23500, 33100]],
                                        	"Flur": [[18400, 24000, 22200, 27100], [19500, 27100, 21600, 30400]],
                                        	"Wohnzimmer": [[17600, 20000, 21600, 24000], [21600, 19200, 24200, 23200]],
                                        	"Schlafzimmer": [[24100, 23200, 26100, 27400], [22100, 23800, 24100, 27400]],
                                        	"Bad": [[21500, 27500, 23200, 29000]],
                                        };
                                        
                                        // ===============================================================================
                                        
                                        type Rectangle = [number, number, number, number];
                                        
                                        for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                        	createState(`Staubsauger.${room}`, {
                                        		type: "boolean",
                                        		read: true,
                                        		write: true,
                                        		role: "switch",
                                        		name: `${room} saugen`,
                                        	});
                                        	on({ id: `javascript.${instance}.Staubsauger.${room}`, val: true, ack: false }, async () => {
                                        		if (getState(idVacuumState).val !== 8) await cancelCurrentAction();
                                        		
                                        		cleanRoom(room);
                                        	});
                                        }
                                        
                                        createState(`Staubsauger.stop`, {
                                        	type: "boolean",
                                        	read: true,
                                        	write: true,
                                        	role: "switch",
                                        	name: `Staubsauger anhalten`,
                                        });
                                        on({ id: `javascript.${instance}.Staubsauger.stop`, val: true, ack: false }, () => {
                                        	stopCleanup();
                                        });
                                        
                                        // Reset all control states when the vacuum starts charging
                                        on({ id: idVacuumState, val: 8 /* charging */ }, (obj) => {
                                        	for (const room of Object.keys(rooms) as (keyof typeof rooms)[]) {
                                        		setState(`Staubsauger.${room}`, false, true);
                                        	}
                                        	setState(`Staubsauger.stop`, false, true);
                                        });
                                        
                                        async function cancelCurrentAction(): Promise<void> {
                                        	setState("mihome-vacuum.0.control.pause", true);
                                        	// wait for the "paused" status before going home
                                        	await waitForPauseOrSleep();
                                        }
                                        
                                        async function stopCleanup(): Promise<void> {
                                        	log(`Saugvorgang abgebrochen!`);
                                        	setState(`Staubsauger.stop`, true, true);
                                        
                                        	if (getState(idVacuumState).val !== 8) {
                                        		await cancelCurrentAction();
                                        
                                        		await setStateAsync('mihome-vacuum.0.control.home', true);
                                        		// wait for the "charging" status before resolving
                                        		await waitFor(idVacuumState, 8);
                                        	}
                                        
                                        	log(`Staubsauger ist in der Basis`);
                                        
                                        	setState(`Staubsauger.stop`, false, true);
                                        }
                                        
                                        async function cleanRoom(room: keyof typeof rooms): Promise<void> {
                                        	log(`Saugvorgang für ${room} gestartet!`);
                                        	setState(`Staubsauger.${room}`, true, true);
                                        
                                        	const roomCoords = rooms[room];
                                        
                                        	const coords = roomCoords[0];
                                        	const targetCoords = [
                                        		((coords[0] + coords[2]) / 2).toFixed(0),
                                        		((coords[1] + coords[3]) / 2).toFixed(0),
                                        	]
                                        
                                        	// go to center of first zone
                                        	const gotoString = targetCoords.join(",");
                                        	await setStateAsync("mihome-vacuum.0.control.goTo", gotoString);
                                        	log(`Fahre zur Mitte von ${room}`);
                                        	await wait(10000);
                                        	await waitForPauseOrSleep();
                                        
                                        	if (getState("Staubsauger.stop").val) return;
                                        
                                        	const zoneCleanString = roomCoords.map(zone => {
                                        		return "[" + zone.concat(1).map(coord => coord.toString()).join(",") + "]";
                                        	}).join(",");
                                        
                                        	log("Starte Zonenreinigung...");
                                        	await setStateAsync("mihome-vacuum.0.control.zoneClean", zoneCleanString);
                                        	// wait for the cleanup to finish
                                        	await waitFor(idVacuumState, 6);
                                        }
                                        
                                        async function waitForPauseOrSleep(): Promise<void> {
                                        	log("Warte auf Zustand schlafen oder Pause...");
                                        	switch (getState(idVacuumState).val) {
                                        		case 3:
                                        		case 10:
                                        			log(" => Zustand bereits aktiv!");
                                        			return;
                                        		default:
                                        			await Promise.race([
                                        				waitFor(idVacuumState, 10),
                                        				waitFor(idVacuumState, 3),
                                        			]);
                                        			log(" => Zustand erreicht!");
                                        	}
                                        }
                                        
                                        function wait(ms: number): Promise<void> {
                                        	return new Promise(resolve => {
                                        		setTimeout(resolve, ms);
                                        	});
                                        }
                                        
                                        function waitFor(stateID: string, value: any): Promise<void> {
                                        	return new Promise(resolve => {
                                        		const handler = (obj: iobJS.ChangedStateObject) => {
                                        			if (obj.newState.val === value) {
                                        				unsubscribe(handler);
                                        				resolve();
                                        			}
                                        		}
                                        		subscribe(stateID, handler);
                                        	})
                                        }
                                        
                                        function setStateAsync(id: string, state: any): Promise<void> {
                                        	return new Promise(res => {
                                        		setState(id, state, () => res());
                                        	});
                                        }
                                        

                                        Beide Varianten legen für jeden Raum und für Stop einen State an, den du dann jeweils mit einer Alexa-Routine ansprechen kannst.

                                        M Offline
                                        M Offline
                                        Mor9oth
                                        schrieb am zuletzt editiert von
                                        #41

                                        Hey @AlCalzone kann man dein Script denn auch so anpassen/erweitern, dass es zusätzlich zu den Räumen auch Positionen gibt, die nur angefahren werden könnten? Um z.B. den Staubbehälter vor dem Mülleimer leeren zu können? "Fahre Staubsauger zum Mülleimer"
                                        Zudem wäre es denke ich auch interessant wenn man die Saugstufen in Abhängigkeit machen könnten. Im Bad könnte ich beispielsweise immer eine starke Stufe vertragen da hier unser Katzenklo steht. Oder kann man es in Abhängikeit vom Raum so einstellen, dass der ZoneCleanup auch mehrfach ausgeführt wird? Über ein paar Scripterweiterungen würde ich micht mega freuen :-)

                                        AlCalzoneA 1 Antwort Letzte Antwort
                                        0
                                        • M Mor9oth

                                          Hey @AlCalzone kann man dein Script denn auch so anpassen/erweitern, dass es zusätzlich zu den Räumen auch Positionen gibt, die nur angefahren werden könnten? Um z.B. den Staubbehälter vor dem Mülleimer leeren zu können? "Fahre Staubsauger zum Mülleimer"
                                          Zudem wäre es denke ich auch interessant wenn man die Saugstufen in Abhängigkeit machen könnten. Im Bad könnte ich beispielsweise immer eine starke Stufe vertragen da hier unser Katzenklo steht. Oder kann man es in Abhängikeit vom Raum so einstellen, dass der ZoneCleanup auch mehrfach ausgeführt wird? Über ein paar Scripterweiterungen würde ich micht mega freuen :-)

                                          AlCalzoneA Offline
                                          AlCalzoneA Offline
                                          AlCalzone
                                          Developer
                                          schrieb am zuletzt editiert von
                                          #42

                                          @Mor9oth Gute Ideen. Eventuell komme ich morgen dazu - aber keine Garantie!

                                          Warum `sudo` böse ist: https://forum.iobroker.net/post/17109

                                          M 1 Antwort Letzte Antwort
                                          0
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          847

                                          Online

                                          32.4k

                                          Benutzer

                                          81.4k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2025
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe