]> git.basschouten.com Git - openhab-addons.git/commitdiff
[irobot] Add command "cleanRegions" to clean specific regions only (#9775)
authorrimago <fb@java4.info>
Tue, 26 Jan 2021 04:19:49 +0000 (05:19 +0100)
committerGitHub <noreply@github.com>
Tue, 26 Jan 2021 04:19:49 +0000 (20:19 -0800)
Signed-off-by: Florian Binder <fb@java4.info>
bundles/org.openhab.binding.irobot/README.md
bundles/org.openhab.binding.irobot/src/main/java/org/openhab/binding/irobot/internal/IRobotBindingConstants.java
bundles/org.openhab.binding.irobot/src/main/java/org/openhab/binding/irobot/internal/dto/MQTTProtocol.java
bundles/org.openhab.binding.irobot/src/main/java/org/openhab/binding/irobot/internal/handler/RoombaHandler.java
bundles/org.openhab.binding.irobot/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.irobot/src/test/java/org/openhab/binding/irobot/internal/handler/RoombaHandlerTest.java [new file with mode: 0644]

index 568a0ab98761674d914e6be134bb6ec97db950c3..327ea3815220ae50be1f1b8c10829bc7453e6acb 100644 (file)
@@ -32,7 +32,7 @@ known, however, whether the password is eternal or can change during factory res
 
 | channel       | type   | description                                        | Read-only |
 |---------------|--------|----------------------------------------------------|-----------|
-| command       | String | Command to execute: clean, spot, dock, pause, stop | N |
+| command       | String | Command to execute: clean, cleanRegions, spot, dock, pause, stop | N |
 | cycle         | String | Current mission: none, clean, spot                 | Y |
 | phase         | String | Current phase of the mission; see below.           | Y |
 | battery       | Number | Battery charge in percents                         | Y |
@@ -52,6 +52,7 @@ known, however, whether the password is eternal or can change during factory res
 | always_finish | Switch | Whether to keep cleaning if the bin becomes full   | N |
 | power_boost   | String | Power boost mode: "auto", "performance", "eco"     | N |
 | clean_passes  | String | Number of cleaning passes: "auto", "1", "2"        | N |
+| last_command  | String | Json string containing the parameters of the last executed command | N |
 
 Known phase strings and their meanings:
 
@@ -137,6 +138,14 @@ Error codes. Data type is string in order to be able to utilize mapping to human
 | 75    | Navigation problem        |
 | 76    | Hardware problem detected |
 
+## Cleaning specific regions
+You can clean one or many specific regions of a given map by sending the following String to the command channel:
+
+```
+ cleanRegions:<pmapId>;<region_id1>,<region_id2>,..
+```
+The easiest way to determine the pmapId and region_ids is to monitor the last_command channel while starting a new mission for the specific region with the iRobot-App.
+
 ## Known Problems / Caveats
 
 1. Sending "pause" command during missions other than "clean" is equivalent to sending "stop"
index 8e50cf56a8196d5728947cab629c028ffd3f7c2a..6f96e48e34bf86c7cffb9c3efac4d960d2b27fbb 100644 (file)
@@ -48,8 +48,10 @@ public class IRobotBindingConstants {
     public static final String CHANNEL_ALWAYS_FINISH = "always_finish";
     public static final String CHANNEL_POWER_BOOST = "power_boost";
     public static final String CHANNEL_CLEAN_PASSES = "clean_passes";
+    public static final String CHANNEL_LAST_COMMAND = "last_command";
 
     public static final String CMD_CLEAN = "clean";
+    public static final String CMD_CLEAN_REGIONS = "cleanRegions";
     public static final String CMD_SPOT = "spot";
     public static final String CMD_DOCK = "dock";
     public static final String CMD_PAUSE = "pause";
index 584e7748ae7f82ef35aefd7af2b85cc374d59ab7..30ab3722ef30dccb05659c4fc50cb6e91406de76 100644 (file)
  */
 package org.openhab.binding.irobot.internal.dto;
 
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.google.gson.JsonElement;
+
 /**
  * iRobot MQTT protocol messages
  *
  * @author Pavel Fedin - Initial contribution
+ * @author Florian Binder - Added CleanRoomsRequest
  *
  */
 public class MQTTProtocol {
@@ -23,6 +30,29 @@ public class MQTTProtocol {
         public String getTopic();
     }
 
+    public static class CleanRoomsRequest extends CommandRequest {
+        public int ordered;
+        public String pmap_id;
+        public List<Region> regions;
+
+        public CleanRoomsRequest(String cmd, String mapId, String[] regions) {
+            super(cmd);
+            ordered = 1;
+            pmap_id = mapId;
+            this.regions = Arrays.stream(regions).map(i -> new Region(i)).collect(Collectors.toList());
+        }
+
+        public static class Region {
+            public String region_id;
+            public String type;
+
+            public Region(String id) {
+                this.region_id = id;
+                this.type = "rid";
+            }
+        }
+    }
+
     public static class CommandRequest implements Request {
         public String command;
         public long time;
@@ -31,7 +61,7 @@ public class MQTTProtocol {
         public CommandRequest(String cmd) {
             command = cmd;
             time = System.currentTimeMillis() / 1000;
-            initiator = "localApp";
+            initiator = "openhab";
         }
 
         @Override
@@ -56,6 +86,7 @@ public class MQTTProtocol {
     public static class CleanMissionStatus {
         public String cycle;
         public String phase;
+        public String initiator;
         public int error;
     }
 
@@ -183,6 +214,9 @@ public class MQTTProtocol {
         public String bootloaderVer;
         // "umiVer":"6",
         public String umiVer;
+        // "lastCommand":
+        // {"command":"start","initiator":"localApp","time":1610283995,"ordered":1,"pmap_id":"AAABBBCCCSDDDEEEFFF","regions":[{"region_id":"6","type":"rid"}]}
+        public JsonElement lastCommand;
     }
 
     // Data comes as JSON string: {"state":{"reported":<Actual content here>}}
index 943f4de955a272036b6cea2df4246c9d7f58d64e..39a51da8b8e081d22c16d5983058ec2e8d99bc92 100644 (file)
@@ -25,6 +25,7 @@ import java.util.Hashtable;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.regex.Pattern;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -65,6 +66,7 @@ import com.google.gson.stream.JsonReader;
  *
  * @author hkuhn42 - Initial contribution
  * @author Pavel Fedin - Rewrite for 900 series
+ * @author Florian Binder - added cleanRegions command and lastCommand channel
  */
 @NonNullByDefault
 public class RoombaHandler extends BaseThingHandler implements MqttConnectionObserver, MqttMessageSubscriber {
@@ -128,7 +130,24 @@ public class RoombaHandler extends BaseThingHandler implements MqttConnectionObs
                     cmd = isPaused ? "resume" : "start";
                 }
 
-                sendRequest(new MQTTProtocol.CommandRequest(cmd));
+                if (cmd.startsWith(CMD_CLEAN_REGIONS)) {
+                    // format: cleanRegions:<pmid>;<region_id1>,<region_id2>,...
+                    if (Pattern.matches("cleanRegions:[^:;,]+;.+(,[^:;,]+)*", cmd)) {
+                        String[] cmds = cmd.split(":");
+                        String[] params = cmds[1].split(";");
+
+                        String mapId = params[0];
+                        String[] regionIds = params[1].split(",");
+
+                        sendRequest(new MQTTProtocol.CleanRoomsRequest("start", mapId, regionIds));
+                    } else {
+                        logger.warn("Invalid request: {}", cmd);
+                        logger.warn("Correct format: cleanRegions:<pmid>;<region_id1>,<region_id2>,...>");
+                    }
+                } else {
+                    sendRequest(new MQTTProtocol.CommandRequest(cmd));
+                }
+
             }
         } else if (ch.startsWith(CHANNEL_SCHED_SWITCH_PREFIX)) {
             MQTTProtocol.Schedule schedule = lastSchedule;
@@ -489,6 +508,10 @@ public class RoombaHandler extends BaseThingHandler implements MqttConnectionObs
             }
         }
 
+        if (reported.lastCommand != null) {
+            reportString(CHANNEL_LAST_COMMAND, reported.lastCommand.toString());
+        }
+
         reportProperty(Thing.PROPERTY_FIRMWARE_VERSION, reported.softwareVer);
         reportProperty("navSwVer", reported.navSwVer);
         reportProperty("wifiSwVer", reported.wifiSwVer);
index b13b25c7af194457cb2446a777011fb04b99d4d7..88101263c0ae5021dc51f6c78c974a7a3cd8e615 100644 (file)
@@ -10,6 +10,7 @@
 
                <channels>
                        <channel id="command" typeId="command"/>
+                       <channel id="last_command" typeId="lastCommand"/>
                        <channel id="cycle" typeId="cycle"/>
                        <channel id="phase" typeId="phase"/>
                        <channel id="battery" typeId="battery"/>
                </state>
        </channel-type>
 
+       <channel-type id="lastCommand" advanced="true">
+               <item-type>String</item-type>
+               <label>Last Command</label>
+               <description>The last command which has been received by the iRobot</description>
+               <state readOnly="true">
+               </state>
+       </channel-type>
+
 </thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.irobot/src/test/java/org/openhab/binding/irobot/internal/handler/RoombaHandlerTest.java b/bundles/org.openhab.binding.irobot/src/test/java/org/openhab/binding/irobot/internal/handler/RoombaHandlerTest.java
new file mode 100644 (file)
index 0000000..afb9b82
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.irobot.internal.handler;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandlerCallback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.LocationAwareLogger;
+
+/**
+ * Test the MQTT protocol with local iRobot (without openhab running).
+ * This class is used to test the binding against a local iRobot instance.
+ *
+ * @author Florian Binder - Initial contribution
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestInstance(Lifecycle.PER_CLASS)
+class RoombaHandlerTest {
+
+    private static final String IP_ADDRESS = "<iRobotIP>";
+    private static final String PASSWORD = "<PasswordForIRobot>";
+
+    private RoombaHandler handler;
+    private @Mock Thing myThing;
+    private ThingHandlerCallback callback;
+
+    @BeforeEach
+    void setUp() throws Exception {
+        Logger l = LoggerFactory.getLogger(RoombaHandler.class);
+        Field f = l.getClass().getDeclaredField("currentLogLevel");
+        f.setAccessible(true);
+        f.set(l, LocationAwareLogger.TRACE_INT);
+
+        Configuration config = new Configuration();
+        config.put("ipaddress", RoombaHandlerTest.IP_ADDRESS);
+        config.put("password", RoombaHandlerTest.PASSWORD);
+
+        Mockito.when(myThing.getConfiguration()).thenReturn(config);
+        Mockito.when(myThing.getUID()).thenReturn(new ThingUID("mocked", "irobot", "uid"));
+
+        callback = Mockito.mock(ThingHandlerCallback.class);
+
+        handler = new RoombaHandler(myThing);
+        handler.setCallback(callback);
+    }
+
+    // @Test
+    void testInit() throws InterruptedException, IOException {
+        handler.initialize();
+        Mockito.verify(myThing, Mockito.times(1)).getConfiguration();
+
+        System.in.read();
+        handler.dispose();
+    }
+
+    // @Test
+    void testCleanRegion() throws IOException, InterruptedException {
+        handler.initialize();
+
+        System.in.read();
+
+        ChannelUID cmd = new ChannelUID("my:thi:blabla:command");
+        handler.handleCommand(cmd, new StringType("cleanRegions:AABBCCDDEEFFGGHH;2,3"));
+
+        System.in.read();
+        handler.dispose();
+    }
+
+    // @Test
+    void testDock() throws IOException, InterruptedException {
+        handler.initialize();
+
+        System.in.read();
+
+        ChannelUID cmd = new ChannelUID("my:thi:blabla:command");
+        handler.handleCommand(cmd, new StringType("dock"));
+
+        System.in.read();
+        handler.dispose();
+    }
+
+    // @Test
+    void testStop() throws IOException, InterruptedException {
+        handler.initialize();
+
+        System.in.read();
+
+        ChannelUID cmd = new ChannelUID("my:thi:blabla:command");
+        handler.handleCommand(cmd, new StringType("stop"));
+
+        System.in.read();
+        handler.dispose();
+    }
+}