Xiaomi Robot Vacuum Binding

is the scaling interface included in 2.5.5 stable binding?

No it is not… currently the only way to change is to change the scale and recompile yourself
Here is the scale:

I still have threads running up with that downloaded version


being on OH 2.5.5 I am still unable to login into the cloud. @marcel: any hint greatly appreciated.


16:01:38.234 [DEBUG] [.miio.internal.cloud.MiCloudConnector] - Xiaomi cloud login with userid nXXXXX@XXX.XXX
16:01:38.245 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi Login step 1
16:01:46.356 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi Login step 1 content response= &&&START&&&{"serviceParam":"{\"checkSafePhone\":false}","qs":"%3Fsid%3Dxiaomiio%26_json%3Dtrue","code":70016,"captchaUrl":null,"callback":"https://sts.api.io.mi.com/sts","location":"","_sign":"0psXfr43eNI0IX6q9Suk3qWbRqU=","desc":"登录验证失败","sid":"xiaomiio"}
16:01:46.368 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi Login step 1 response = HttpContentResponse[HTTP/1.1 200 OK - 268 bytes]
16:01:46.376 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi Login step 1 sign = 0psXfr43eNI0IX6q9Suk3qWbRqU=
16:01:46.386 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi Login step 2
16:01:46.833 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login step 2 response = HttpContentResponse[HTTP/1.1 200 OK - 1373 bytes]
16:01:46.839 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login step 2 content = &&&START&&&{"sid":"xiaomiio","callback":"https://sts.api.io.mi.com/sts","qs":"%3Fsid%3Dxiaomiio%26_json%3Dtrue","securityStatus":1,"notificationUrl":"/identity/authStart?sid=passport&context=zu39Doq%2B%2B6Xfv4gbrzho572zTvFtZfmwaU%2FWHtu4X7tf2PJGEldEqiJpsrEzz9zo3r0o%2FpqLY22EbZ9bx0Tort4E1XA51cpJwaoycgNxV6nzBBHcfCo5iH40skzIyVt5OQeWEzbfqui353nqibNd3kxGbXAHWjm4yk1lgEwDX54h5ai9QiwW1O3omT5po6vBXAaUEHRy36qDPNY7u%2F2WWs3fpEUh0vEll2ZTCSKIu4QBqjk0Sr7ZGWe6bBpfwG5JXaSllwSlTUG%2FCLfHgcpe1R9zEiZv0zcM0KO45YjzVVkj0gUMZQg3AuqqBsCRE75%2FpJMwYfVgV8kNuAxZhNiE6i7LaL%2Focu6Vur9X5clJ9fovzgt4U3mGfPwIP1dM0oAQX68eNbBnguoHp1hvvMKNXIFYGaPdBPAcg2De2C5CKTAx8gkrQZ46ttf55YKbvv77jpJ%2B9xtX2owFcCI5hGcz8CHblooAChwsUie%2FPhhY23cjMXZspRavFgphaq%2BPbEmt6FM98gAm3C1baBvliStZ8Ajl1tQzXorJCjSoZZBgyzb0sz87Ni5yU77%2FVqCxPgQ2CWQoc%2BVu%2BN7NSO97epIc5XtvGgbiVF8mIgCzM%2FeiM1M8dCDCHgynSsNxoJLm0%2B%2F%2F3UPbihWzGSgxh5qvmYSw5ut5V5FjC5XG4chlBqrxWmylfpU7IRucAqbd2954NPulaDIe0TWRgv%2Bzo0dvruepfGkypDaSjzDGY%2FPjKKqbhWMoRfCA1a9EeAgfPfw163VO5It2SL9gIuTZxQqqvgeouC6ZZ8vHiRbsQWRi8ZzWFeQA4VVt5mpLtUjdu7XbSrllXlroxNjv9QeFBYLZvipFKsUEDH12edatZRh2JWfWrG3gwqJYFeUqN%2F4%2FVI%2BOCQF19cLP57CwTWD2kcr9YNqpfXAhSLjjwQDXDY1o4rE6JcUGiY8Ap0YaWjQ8wwaLUF%2FnpSiU7xZM1cLF36xMFDopFHR6eLxCrMhzV2k3pzBAUR%2FEPSvb0FjoblGRx7%2BtUeGV","pwd":0,"code":0,"desc":"成功","location":"","captchaUrl":null,"_sign":"0psXfr43eNI0IX6q9Suk3qWbRqU="}
16:01:46.854 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login ssecurity =
16:01:46.859 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login userId =
16:01:46.863 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login cUserId =
16:01:46.867 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login passToken =
16:01:46.871 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login location =
16:01:46.875 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Xiaomi login code = 0
16:01:46.881 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie dump for https://account.xiaomi.com/pass/serviceLoginAuth2
16:01:46.888 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie (.xiaomi.com) : sdkVersion --> accountsdk-18.8.15     (path: /. Removed: false)
16:01:46.894 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie (.xiaomi.com) : deviceId --> dogvyo     (path: /. Removed: false)
16:01:46.902 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie (.xiaomi.com) : uLocale --> de_CH     (path: /. Removed: false)
16:01:46.908 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie (account.xiaomi.com) : pass_trace --> vImI3ISiEI4KJ0P2raNJ2CpCDMinz5lRLOXtXXzbP4WzslzjX603OiSjIKprzSaJr1twUp/JFhPiB/QdyTjTDw==     (path: /. Removed: false)
16:01:46.915 [TRACE] [.miio.internal.cloud.MiCloudConnector] - Cookie (account.xiaomi.com) : pass_ua --> web     (path: /. Removed: false)
16:01:46.935 [INFO ] [.miio.internal.cloud.MiCloudConnector] - Error logging on to Xiaomi cloud (0): Error getting logon location URL. Return code: 0

Hello everyone.
I tried everything, but I don’t get my vacuum roborock s6 to work with the mi io binding. :frowning:

The Thing shows the follwoing status no matter what I do.

Status: OFFLINE - COMMUNICATION_ERROR No Response from device

I already updated openhab from 2.4 to the latest 2.5.5 and reinstalled the mi io binding (2.5.5). (Because the roborock s6 was not listet in the binding version 2.4) I created the thing via paper ui, deleted that, created it via thing file… nothing works.

I took the token from an old mi home app version.

Anyone an idea? I read that a few of you get it to work with the s6…
Btw.: Via mobile the vac can be controlled as normal.

Thanks in advance.

code 70016 = wrongs userID/Password. Double check your credentials.

@masterds163 try to get the right token via the cloud. Your error is typical for wrong token.
the S6 should be recognized as vacuum with the 2.5.5 binding.
If issues persist, share a debug log… bug again, if there are no responses, the token is wrong.

@marcel_verpaalen: I have “Error getting logon location URL. Return code: 0”
So token should not be my problem? Any idea?

It is the cloud user id/pass that is wrong.

Of you can’t get that to work, use ‘the old way’ of getting the token. See the first topic

Marcel, thank your for your fast response! I already have the token using the old way so the binding is running fine but without cloud access and map. The cloud id/pass I use in the binding is the same I am using in the Mi Home App on Android. I am on “de” server. I have no clue what’s going wrong.

Thanks for the answer Marcel. And how do I get the token out of the cloud? Is there a manual/tutorial for this? (Quick google serach does not help)

Ok, I found the settings in the binding config…
Now I deleted the thing file (token must be set) and try to discover the robot via thing adding in paper ui. The robot is not longer discovered from the binding… :frowning: It only shows other mi things which i added via Xiaomi Mi Smart Home Binding.

Is there a way to check if the binding has access to the cloud?

I got it. After i found out, that the IP Adress in the tokens.json file was incorrect, i thought why should the cloud have the wrong IP even if the robot works via app? Solution was quit simple: I have two accounts - one xiaomi mii account, and one roborock account. So the old account data was not updated. After i changed the account settings and deleted the json file, the new data was collected with the right token etc.
Maybe this info is helpfull for someone with similar problems.

Hi @marcel_verpaalen,

I stuck setting up with the latest version.

Thing miio:vacuum:XXXXX "Xiaomi Robi" [ host="ABC", token="EFG",deviceId="XXXXX", model="rockrobo.vacuum.v1" ]

My device gets into configuration-error after some minutes being online. No idea why and how. If I make a change in the thing file, it is online for some minutes.

2020-05-22 13:59:01.666 [INFO ] [st.core.internal.thing.ThingResource] - Received HTTP PUT request for update configuration at 'things/miio:vacuum: XXXXXX/config' for an unmanaged thing 'miio:vacuum:XXXXXX'.
2020-05-22 13:59:51.660 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from OFFLINE (CONFIGURATION_ERROR) to UNINITIALIZED
2020-05-22 13:59:53.848 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from UNINITIALIZED to OFFLINE (CONFIGURATION_ERROR)
2020-05-22 13:59:53.858 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from OFFLINE (CONFIGURATION_ERROR) to UNINITIALIZED (HANDLER_MISSING_ERROR)
2020-05-22 13:59:54.081 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from UNINITIALIZED (HANDLER_MISSING_ERROR) to INITIALIZING
2020-05-22 13:59:54.110 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from INITIALIZING to OFFLINE
2020-05-22 13:59:54.126 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from OFFLINE to ONLINE
2020-05-22 14:00:38.698 [WARN ] [.core.thing.binding.BaseThingHandler] - Handler MiIoUnsupportedHandler tried updating the thing status although the handler was already disposed.
2020-05-22 14:00:54.262 [hingStatusInfoChangedEvent] - 'miio:vacuum: XXXXXX' changed from ONLINE to OFFLINE (CONFIGURATION_ERROR)

Within the binding config I maintained user, passcode as well as server cn. This server value is not pulled by the thing config, there the field is still blank.

If I put in there cn, log shows

==> /var/log/openhab2/openhab.log <==
2020-05-22 14:09:31.887 [INFO ] [st.core.internal.thing.ThingResource] - Received HTTP PUT request for update configuration at 'things/miio:vacuum:XXXXXX/config' for an unmanaged thing 'miio:vacuum:XXXXX'.

and nothing changes, the field is still blank.

Do you have any idea what’s wrong here?

Thanks for any hint.


Hey guys. Is it actually possible to send that resume command?

Did a small improvement request:

Really appreciate the binding and the way it has improved lately!

I think i encountered a small bug. The Roborock 1S is working great, but the buttons flip into the wrong state. When i click vacuum, the bot starts to work, but the switch button switched to dock being selected. When i click dock, it selects vacuum. It looks like the buttons are one step behind the actual state. When i look in the eventlog i can confirm this is happening from within the binding.
If someone can check if i’m not mistaken, i’ll file a github issue.

Using openHAB 2.5.5

String verd0_stofzuiger_actionControl “Vacuum Control” {channel=“miio:vacuum:xxxxxx:actions#control”}

Switch item=verd0_stofzuiger_actionControl mappings=[vacuum=“Vacuum”, pause=“Pause”, dock=“Dock”]


**2020-05-24 10:43:31.051 [ome.event.ItemCommandEvent] - Item 'verd0_stofzuiger_actionControl' received command vacuum**
2020-05-24 10:43:31.055 [nt.ItemStatePredictedEvent] - verd0_stofzuiger_actionControl predicted to become vacuum
2020-05-24 10:43:31.060 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from dock to vacuum
2020-05-24 10:43:31.152 [vent.ItemStateChangedEvent] - verd0_stofzuiger_statusDND changed from ON to OFF
**2020-05-24 10:43:31.155 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from vacuum to dock**

2020-05-24 10:43:38.873 [ome.event.ItemCommandEvent] - Item 'verd0_stofzuiger_actionControl' received command dock
2020-05-24 10:43:38.879 [nt.ItemStatePredictedEvent] - verd0_stofzuiger_actionControl predicted to become dock
2020-05-24 10:43:38.988 [vent.ItemStateChangedEvent] - verd0_stofzuiger_statusArea changed from 3.1375 to 0.0
2020-05-24 10:43:38.989 [vent.ItemStateChangedEvent] - verd0_stofzuiger_statusTime changed from 2 to 0
2020-05-24 10:43:38.989 [vent.ItemStateChangedEvent] - verd0_stofzuiger_statusClean changed from 0 to 1
2020-05-24 10:43:38.990 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from dock to vacuum

2020-05-24 10:44:43.522 [ome.event.ItemCommandEvent] - Item 'verd0_stofzuiger_actionControl' received command vacuum
2020-05-24 10:44:43.528 [nt.ItemStatePredictedEvent] - verd0_stofzuiger_actionControl predicted to become vacuum
2020-05-24 10:44:43.534 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from dock to vacuum
2020-05-24 10:44:43.563 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from vacuum to dock

2020-05-24 10:44:59.319 [ome.event.ItemCommandEvent] - Item 'verd0_stofzuiger_actionControl' received command dock
2020-05-24 10:44:59.326 [nt.ItemStatePredictedEvent] - verd0_stofzuiger_actionControl predicted to become dock
2020-05-24 10:44:59.330 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from vacuum to dock
2020-05-24 10:44:59.369 [vent.ItemStateChangedEvent] - verd0_stofzuiger_actionControl changed from dock to vacuum

Same issue here on my 1S. Even setting the refresh interval of the Thing to a very short time seems not to fix it.

I expect that may even make it worse…
Mostly when this happens is because the properties are still being refreshed.

All commands are put in a queue, meaning that there may still be commands being in the queue (like the various commands of a refresh). They will be executed in sequence. After all commands, the binding is anyway does a refresh or properties.

Setting the refresh very fast, like every few seconds, has very high chance that there are still several refreshes in the queue. You can see that cuz the robot has not yet processed your vacuum/go to dock/whatever command, the refresh will set the status to whatever it was before.

In that case, If you wait a few seconds the right status will be set

I’m using the default settings and if i give the command, it instantly switches back to the previous state. I did wait a few seconds and it did not turn into the right state. How long should i wait? From a developers point of view this might look good, but from a users persepctive (e.g. my perspective) it seems wrong, i issue a command and the buttons flips to a state i did not set.

Hello Marcel,

sorry, did take some time. This is feedback:

2020-05-27 22:33:43.094 [INFO ] [g.miio.internal.cloud.CloudConnector] - Getting vacuum map rubyslite%2F261631712%2F2 from Xiaomi cloud server: de

2020-05-27 22:33:44.148 [WARN ] [mmon.WrappedScheduledExecutorService] - Scheduled runnable ended with an exception: 

java.lang.Error: Probable fatal error:No fonts found.

	at sun.font.SunFontManager.getDefaultPhysicalFont(SunFontManager.java:1236) ~[?:1.8.0_252]

	at sun.font.SunFontManager.initialiseDeferredFont(SunFontManager.java:1106) ~[?:1.8.0_252]

	at sun.font.CompositeFont.doDeferredInitialisation(CompositeFont.java:287) ~[?:1.8.0_252]

	at sun.font.CompositeFont.getSlotFont(CompositeFont.java:376) ~[?:1.8.0_252]

	at sun.font.CompositeStrike.getStrikeForSlot(CompositeStrike.java:82) ~[?:1.8.0_252]

	at sun.font.CompositeStrike.getFontMetrics(CompositeStrike.java:97) ~[?:1.8.0_252]

	at sun.font.FontDesignMetrics.initMatrixAndMetrics(FontDesignMetrics.java:359) ~[?:1.8.0_252]

	at sun.font.FontDesignMetrics.<init>(FontDesignMetrics.java:350) ~[?:1.8.0_252]

	at sun.font.FontDesignMetrics.getMetrics(FontDesignMetrics.java:302) ~[?:1.8.0_252]

	at sun.java2d.SunGraphics2D.getFontMetrics(SunGraphics2D.java:855) ~[?:1.8.0_252]

	at org.openhab.binding.miio.internal.robot.RRMapDraw.drawOpenHabRocks(RRMapDraw.java:345) ~[?:?]

	at org.openhab.binding.miio.internal.robot.RRMapDraw.getImage(RRMapDraw.java:414) ~[?:?]

	at org.openhab.binding.miio.internal.handler.MiIoVacuumHandler.getMap(MiIoVacuumHandler.java:478) ~[?:?]

	at org.openhab.binding.miio.internal.handler.MiIoVacuumHandler.lambda$5(MiIoVacuumHandler.java:451) ~[?:?]

	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[?:1.8.0_252]

	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[?:1.8.0_252]

	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) ~[?:1.8.0_252]

	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) ~[?:1.8.0_252]

	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_252]

	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_252]

	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_252]

Is this from my ‘fixed version’ … I guess than it still did work, right?


I am new to openHAB but actually very impressed about it. It took some time to understand the concept behind. Thanks for the great binding and thanks to the community for sharing posts, comments and code snippets.
Now it’s time to give back something, so I would like to share my config with you. Maybe this will help someone who is in the same situation as I was a few days ago. Any comments and improvements are welcome!

This is my requirement:

  • select one or more rooms (=segments) or zones
  • preserve order from manual selection. Example: Select rooms in this order: 1, 5, 3. Cleaning will start with room 1 and finish with room 3
  • in addition to select each room individually, all rooms may be selected as well
  • runs (for me) with a Roborock S5 Max
  • my wife is able to control it

The result looks like this:


Here is my items file:

// ----------------------------------------------------------------------------
// Zonenreinigung
// ----------------------------------------------------------------------------
Group gZoneControl                          "Zonenreinigung"                            <fan>       (gVac)
Group gZone                                 "Zonen"                                     <group>     (gZoneControl)
Group:DateTime:LATEST gZoneLastUpdate       "Letzte Änderung [%s]"                      <time>      (gZone)
Group gZoneSwitch                           "Schalter"                                  <switch>    (gZone)

Group:Switch:AND(ON,OFF) gAllZones       	"Alle Zonen"                                <switch>     

Group gEsstisch                             "Esstisch - Gruppe"                         <status>    (gZone)
Switch zEsstisch                            "Esstisch"                                  <girl_2>    (gEsstisch, gZoneSwitch)
DateTime zEsstischLastUpdate                "Esstisch - letzte Änderung [%s]"           <time>      (gEsstisch, gZoneLastUpdate)

Group gKueche                               "Küche - Gruppe"                            <status>    (gZone)
Switch zKueche                              "Küche"                                     <kitchen>   (gKueche, gZoneSwitch, gAllZones)
DateTime zKuecheLastUpdate                  "Küche - letzte Änderung [%s]"              <time>      (gKueche, gZoneLastUpdate)

Group gToilette                             "Toilette - Gruppe"                         <status>    (gZone)
Switch zToilette                            "Toilette"                                  <toilet>    (gToilette, gZoneSwitch, gAllZones)
DateTime zToiletteLastUpdate                "Toilette - letzte Änderung [%s]"           <time>      (gToilette, gZoneLastUpdate)

Group gSpeisekammer                         "Speisekammer - Gruppe"                     <status>    (gZone)
Switch zSpeisekammer                        "Speisekammer"                              <pantry>    (gSpeisekammer, gZoneSwitch, gAllZones)
DateTime zSpeisekammerLastUpdate            "Speisekammer - letzte Änderung [%s]"       <time>      (gSpeisekammer, gZoneLastUpdate)

Group gGarderobe                            "Garderobe - Gruppe"                        <status>    (gZone)
Switch zGarderobe                           "Garderobe"                                 <wardrobe>  (gGarderobe, gZoneSwitch, gAllZones)
DateTime zGarderobeLastUpdate               "Garderobe - letzte Änderung [%s]"          <time>      (gGarderobe, gZoneLastUpdate)

Group gWohnzimmer                           "Wohnzimmer - Gruppe"                       <status>    (gZone)
Switch zWohnzimmer                          "Wohnzimmer"                                <sofa>      (gWohnzimmer, gZoneSwitch, gAllZones)
DateTime zWohnzimmerLastUpdate              "Wohnzimmer - letzte Änderung [%s]"         <time>      (gWohnzimmer, gZoneLastUpdate)

Group gFlur                                 "Flur - Gruppe"                             <status>    (gZone)
Switch zFlur                                "Flur"                                      <corridor>  (gFlur, gZoneSwitch, gAllZones)
DateTime zFlurLastUpdate                    "Flur - letzte Änderung [%s]"               <time>      (gFlur, gZoneLastUpdate)

Group gMalzimmer                            "Malzimmer - Gruppe"                        <status>    (gZone)
Switch zMalzimmer                           "Malzimmer"                                 <office>    (gMalzimmer, gZoneSwitch, gAllZones)
DateTime zMalzimmerLastUpdate               "Malzimmer - letzte Änderung [%s]"          <time>      (gMalzimmer, gZoneLastUpdate)

String zoneAction                           "Action"                                    <fan>       (gZoneControl)

the rules:

// ----------------------------------------------------------------------------
rule "zone control - zone selection"
// ----------------------------------------------------------------------------
    Member of gZoneSwitch received update
    logInfo("zone.rules", "update received from item: " + triggeringItem)

    val zoneSwitch = triggeringItem as SwitchItem
    val lastUpdate = now

    zoneSwitch.getGroupNames().forEach[groupName | 
        if (groupName != "gZoneLastSwitch")
            logDebug("zone.rules", "find related members of " + groupName + " in other group gZoneLastUpdate from triggering item " + triggeringItem.name)

            gZoneLastUpdate.members.filter[updateItem | !updateItem.getGroupNames.contains(groupName)].forEach[ updateItem | 
                logInfo("zone.rules", "posting update to " + updateItem.name)
            logDebug("zone.rules", "ignoring group " + groupName) 

// ----------------------------------------------------------------------------
rule "zone control - action"
// ----------------------------------------------------------------------------
    Item zoneAction received command
    logInfo("zone.rules", "zoneAction command received: " + receivedCommand)

    var StringBuilder zoneCmd = new StringBuilder
    var StringBuilder roomCmd = new StringBuilder

    // use this to get the room numbers
    // actionCommand.sendCommand("get_room_mapping[]")

    if (gAllZones.state == ON)
        logInfo("zone.rules", "sending cmd to device: app_start")

    // sort by timestamp and loop throug items
    gZoneLastUpdate.members.sortBy[(state as DateTimeType).getZonedDateTime.toInstant.toEpochMilli].forEach[zoneLastUpdateItem | 
        logDebug("zone.rules", "--> "+ zoneLastUpdateItem)

        // loop until we find the right group...
        zoneLastUpdateItem.getGroupNames().forEach[groupName | 
            logDebug("zone.rules", "----> "+ groupName)
            if (groupName != "gZoneLastUpdate")
                logDebug("zone.rules", "find related members of " + groupName + " in other group gZoneSwitch")

                // find the switch item from the same room group
                gZoneSwitch.members.filter[zoneSwitchItem | 
                        &&  zoneSwitchItem.getGroupNames.contains("gZoneSwitch")].forEach[ zoneSwitchItem | 
                    logDebug("zone.rules", "found related switch item " + zoneSwitchItem.name + " with state " + zoneSwitchItem.state)
                    if (zoneSwitchItem.state == ON)
                        var zoneType = transform("MAP", "robo_room_coordinates.map", zoneSwitchItem.name + "#type")
                        var zoneCoordinates = transform("MAP", "robo_room_coordinates.map", zoneSwitchItem.name + "#coordinates")

                        logInfo("zone.rules", zoneCoordinates + "-" + zoneSwitchItem.name + " is ON. {zoneType:" + zoneType + "; zoneCoordinates: " + zoneCoordinates + "}")

                        // not sure if we can assign a new var by reference, so to be safe, just use if..else and duplicate code
                        if (zoneType == "zone")
                            if (zoneCmd.length == 0)

                        else if (zoneType == "room")
                            if (roomCmd.length == 0)
                                roomCmd.append("app_segment_clean[" + zoneCoordinates)
                                roomCmd.append(", " + zoneCoordinates)

                        logDebug("zone.rules", roomCmd.toString)

    // if we have rooms AND zones, rooms wins - duplicate code here as well
    if (roomCmd.length != 0)
        val cmd = roomCmd.toString + "]"
        logInfo("zone.rules", "sending cmd to device: " + cmd )
    else if (zoneCmd.length != 0)
        val cmd = zoneCmd.toString + "]"
        logInfo("zone.rules", "sending cmd to device: " + cmd )
        logInfo("zone.rules", "no zone or room selected. nothing to do.")



// ----------------------------------------------------------------------------
rule "zone control - init"
// ----------------------------------------------------------------------------
    System started
    val lastUpdate = now

    gZoneLastUpdate.members.forEach(item | 

the map:









the sitemap

                Frame  label=Zonenreinigung
                Switch  item=gAllZones label="Komplettreinigung"
                Default item=zEsstisch visibility=[gAllZones==OFF]
                Default item=zKueche visibility=[gAllZones==OFF]
                Default item=zToilette visibility=[gAllZones==OFF]
                Default item=zSpeisekammer visibility=[gAllZones==OFF]
                Default item=zGarderobe visibility=[gAllZones==OFF]
                Default item=zWohnzimmer visibility=[gAllZones==OFF]
                Default item=zFlur visibility=[gAllZones==OFF]
                Default item=zMalzimmer visibility=[gAllZones==OFF]
                Switch item=zoneAction label="Steuerung" mappings=[start="Start"]
                Group item=gZoneLastUpdate label="Details"

Any feedback and improvments are welcome.

I am already looking forward for my next openHAB project :wink: