]> git.basschouten.com Git - openhab-addons.git/commitdiff
[boschindego] Plot location on map (#13179)
authorJacob Laursen <jacob-github@vindvejr.dk>
Thu, 28 Jul 2022 06:39:27 +0000 (08:39 +0200)
committerGitHub <noreply@github.com>
Thu, 28 Jul 2022 06:39:27 +0000 (08:39 +0200)
* Plot location on map
* Invalidate map when requested by service
* Optimize update of raw map

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
bundles/org.openhab.binding.boschindego/README.md
bundles/org.openhab.binding.boschindego/src/main/java/org/openhab/binding/boschindego/internal/DeviceStatus.java
bundles/org.openhab.binding.boschindego/src/main/java/org/openhab/binding/boschindego/internal/config/BoschIndegoConfiguration.java
bundles/org.openhab.binding.boschindego/src/main/java/org/openhab/binding/boschindego/internal/handler/BoschIndegoHandler.java
bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/i18n/boschindego.properties
bundles/org.openhab.binding.boschindego/src/main/resources/OH-INF/thing/thing-types.xml

index 37c66a925755178172e62196f7b0cb09919852b5..0f983e0cf609e7cae79a4028a9c70687814be53a 100644 (file)
@@ -8,12 +8,12 @@ His [Java Library](https://github.com/zazaz-de/iot-device-bosch-indego-controlle
 
 Currently the binding supports  ***indego***  mowers as a thing type with these configuration parameters:
 
-| Parameter             | Description                                                             | Default |
-|-----------------------|-------------------------------------------------------------------------|---------|
-| username              | Username for the Bosch Indego account                                   |         |
-| password              | Password for the Bosch Indego account                                   |         |
-| refresh               | The number of seconds between refreshing device state                   | 180     |
-| cuttingTimeMapRefresh | The number of minutes between refreshing last/next cutting time and map | 60      |
+| Parameter          | Description                                                     | Default |
+|--------------------|-----------------------------------------------------------------|---------|
+| username           | Username for the Bosch Indego account                           |         |
+| password           | Password for the Bosch Indego account                           |         |
+| refresh            | The number of seconds between refreshing device state           | 180     |
+| cuttingTimeRefresh | The number of minutes between refreshing last/next cutting time | 60      |
 
 ## Channels
 
index fbba18e2d850682d7d8ab3172fb5ff615f6472a7..e6008e4f9e57642572bcc7a6fb4dec053cb8220c 100644 (file)
@@ -29,6 +29,8 @@ import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
 @NonNullByDefault
 public class DeviceStatus {
 
+    public static final int STATE_LEARNING_LAWN = 516;
+
     private final static String STATE_PREFIX = "indego.state.";
     private final static String STATE_UNKNOWN = "unknown";
 
@@ -45,7 +47,7 @@ public class DeviceStatus {
             entry(513, new DeviceStatus("mowing", false, DeviceCommand.MOW)),
             entry(514, new DeviceStatus("relocalising", false, DeviceCommand.MOW)),
             entry(515, new DeviceStatus("loading-map", false, DeviceCommand.MOW)),
-            entry(516, new DeviceStatus("learning-lawn", false, DeviceCommand.MOW)),
+            entry(STATE_LEARNING_LAWN, new DeviceStatus("learning-lawn", false, DeviceCommand.MOW)),
             entry(517, new DeviceStatus("paused", true, DeviceCommand.PAUSE)),
             entry(518, new DeviceStatus("border-cut", false, DeviceCommand.MOW)),
             entry(519, new DeviceStatus("idle-in-lawn", true, DeviceCommand.MOW)),
index ef34f68b34170910bd467a34c3befe22dabb697b..03148db98a933567031aa5a2119be3d4192badb6 100644 (file)
@@ -25,5 +25,5 @@ public class BoschIndegoConfiguration {
     public @Nullable String username;
     public @Nullable String password;
     public long refresh = 180;
-    public long cuttingTimeMapRefresh = 60;
+    public long cuttingTimeRefresh = 60;
 }
index 90ccf48b4abdf1fcc67dfc988ac4d9d2495cb158..3500ac0477bf93bc1096d5623d89d8100887dc12 100644 (file)
@@ -14,6 +14,8 @@ package org.openhab.binding.boschindego.internal.handler;
 
 import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
 
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
 import java.time.Instant;
 import java.time.ZonedDateTime;
 import java.time.temporal.ChronoUnit;
@@ -40,6 +42,7 @@ import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.PercentType;
 import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.RawType;
 import org.openhab.core.library.types.StringType;
 import org.openhab.core.library.unit.SIUnits;
 import org.openhab.core.library.unit.Units;
@@ -64,6 +67,11 @@ import org.slf4j.LoggerFactory;
 @NonNullByDefault
 public class BoschIndegoHandler extends BaseThingHandler {
 
+    private static final String MAP_POSITION_STROKE_COLOR = "#8c8b6d";
+    private static final String MAP_POSITION_FILL_COLOR = "#fff701";
+    private static final int MAP_POSITION_RADIUS = 10;
+    private static final int MAP_REFRESH_INTERVAL_DAYS = 1;
+
     private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
     private final HttpClient httpClient;
     private final BoschIndegoTranslationProvider translationProvider;
@@ -71,10 +79,12 @@ public class BoschIndegoHandler extends BaseThingHandler {
 
     private @NonNullByDefault({}) IndegoController controller;
     private @Nullable ScheduledFuture<?> statePollFuture;
-    private @Nullable ScheduledFuture<?> cuttingTimeMapPollFuture;
+    private @Nullable ScheduledFuture<?> cuttingTimePollFuture;
     private @Nullable ScheduledFuture<?> cuttingTimeFuture;
     private boolean propertiesInitialized;
     private Optional<Integer> previousStateCode = Optional.empty();
+    private @Nullable RawType cachedMap;
+    private Instant cachedMapTimestamp = Instant.MIN;
 
     public BoschIndegoHandler(Thing thing, HttpClient httpClient, BoschIndegoTranslationProvider translationProvider,
             TimeZoneProvider timeZoneProvider) {
@@ -108,9 +118,8 @@ public class BoschIndegoHandler extends BaseThingHandler {
         previousStateCode = Optional.empty();
         this.statePollFuture = scheduler.scheduleWithFixedDelay(this::refreshStateAndOperatingDataWithExceptionHandling,
                 0, config.refresh, TimeUnit.SECONDS);
-        this.cuttingTimeMapPollFuture = scheduler.scheduleWithFixedDelay(
-                this::refreshCuttingTimesAndMapWithExceptionHandling, 0, config.cuttingTimeMapRefresh,
-                TimeUnit.MINUTES);
+        this.cuttingTimePollFuture = scheduler.scheduleWithFixedDelay(this::refreshCuttingTimesWithExceptionHandling, 0,
+                config.cuttingTimeRefresh, TimeUnit.MINUTES);
     }
 
     @Override
@@ -121,11 +130,11 @@ public class BoschIndegoHandler extends BaseThingHandler {
             pollFuture.cancel(true);
         }
         this.statePollFuture = null;
-        pollFuture = this.cuttingTimeMapPollFuture;
+        pollFuture = this.cuttingTimePollFuture;
         if (pollFuture != null) {
             pollFuture.cancel(true);
         }
-        this.cuttingTimeMapPollFuture = null;
+        this.cuttingTimePollFuture = null;
         pollFuture = this.cuttingTimeFuture;
         if (pollFuture != null) {
             pollFuture.cancel(true);
@@ -166,6 +175,9 @@ public class BoschIndegoHandler extends BaseThingHandler {
     private void handleRefreshCommand(String channelId)
             throws IndegoAuthenticationException, IndegoUnreachableException, IndegoException {
         switch (channelId) {
+            case GARDEN_MAP:
+                // Force map refresh and fall through to state update.
+                cachedMapTimestamp = Instant.MIN;
             case STATE:
             case TEXTUAL_STATE:
             case MOWED:
@@ -185,9 +197,6 @@ public class BoschIndegoHandler extends BaseThingHandler {
             case GARDEN_SIZE:
                 refreshOperatingData();
                 break;
-            case GARDEN_MAP:
-                refreshMap();
-                break;
         }
     }
 
@@ -243,9 +252,19 @@ public class BoschIndegoHandler extends BaseThingHandler {
         DeviceStateResponse state = controller.getState();
         updateState(state);
 
+        if (state.mapUpdateAvailable) {
+            cachedMapTimestamp = Instant.MIN;
+        }
+        refreshMap(state.svgXPos, state.svgYPos);
+
         // When state code changed, refresh cutting times immediately.
         if (previousStateCode.isPresent() && state.state != previousStateCode.get()) {
             refreshCuttingTimes();
+
+            // After learning lawn, trigger a forced map refresh on next poll.
+            if (previousStateCode.get() == DeviceStatus.STATE_LEARNING_LAWN) {
+                cachedMapTimestamp = Instant.MIN;
+            }
         }
         previousStateCode = Optional.of(state.state);
     }
@@ -311,22 +330,33 @@ public class BoschIndegoHandler extends BaseThingHandler {
         }
     }
 
-    private void refreshCuttingTimesAndMapWithExceptionHandling() {
-        try {
-            refreshCuttingTimes();
-            refreshMap();
-        } catch (IndegoAuthenticationException e) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                    "@text/offline.comm-error.authentication-failure");
-        } catch (IndegoException e) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+    private void refreshMap(int xPos, int yPos) throws IndegoAuthenticationException, IndegoException {
+        if (!isLinked(GARDEN_MAP)) {
+            return;
         }
-    }
-
-    private void refreshMap() throws IndegoAuthenticationException, IndegoException {
-        if (isLinked(GARDEN_MAP)) {
-            updateState(GARDEN_MAP, controller.getMap());
+        RawType cachedMap = this.cachedMap;
+        boolean mapRefreshed;
+        if (cachedMap == null
+                || cachedMapTimestamp.isBefore(Instant.now().minus(Duration.ofDays(MAP_REFRESH_INTERVAL_DAYS)))) {
+            this.cachedMap = cachedMap = controller.getMap();
+            cachedMapTimestamp = Instant.now();
+            mapRefreshed = true;
+        } else {
+            mapRefreshed = false;
+        }
+        String svgMap = new String(cachedMap.getBytes(), StandardCharsets.UTF_8);
+        if (!svgMap.endsWith("</svg>")) {
+            if (mapRefreshed) {
+                logger.warn("Unexpected map format, unable to plot location");
+                logger.trace("Received map: {}", svgMap);
+                updateState(GARDEN_MAP, cachedMap);
+            }
+            return;
         }
+        svgMap = svgMap.substring(0, svgMap.length() - 6) + "<circle cx=\"" + xPos + "\" cy=\"" + yPos + "\" r=\""
+                + MAP_POSITION_RADIUS + "\" stroke=\"" + MAP_POSITION_STROKE_COLOR + "\" fill=\""
+                + MAP_POSITION_FILL_COLOR + "\" />\n</svg>";
+        updateState(GARDEN_MAP, new RawType(svgMap.getBytes(), cachedMap.getMimeType()));
     }
 
     private void updateState(DeviceStateResponse state) {
index 0d79b191f21e8c11f9c09510b400269bf47d6cca..fd985085e3c675ba0464c4fb262cbd5542d179a1 100644 (file)
@@ -10,8 +10,8 @@ thing-type.boschindego.indego.description = Indego which supports the connect fe
 
 # thing types config
 
-thing-type.config.boschindego.indego.cuttingTimeMapRefresh.label = Cutting Time/Map Refresh Interval
-thing-type.config.boschindego.indego.cuttingTimeMapRefresh.description = The number of minutes between refreshing last/next cutting time and map.
+thing-type.config.boschindego.indego.cuttingTimeRefresh.label = Cutting Time Refresh Interval
+thing-type.config.boschindego.indego.cuttingTimeRefresh.description = The number of minutes between refreshing last/next cutting time.
 thing-type.config.boschindego.indego.password.label = Password
 thing-type.config.boschindego.indego.password.description = Password for the Bosch Indego account.
 thing-type.config.boschindego.indego.refresh.label = Refresh Interval
index 29da1879e2b6f3638215a44229e726d68b5568e0..b4d87a017daaebe694cd61beabbd1a4faa7dfc0b 100644 (file)
@@ -38,9 +38,9 @@
                                <description>The number of seconds between refreshing device state.</description>
                                <default>180</default>
                        </parameter>
-                       <parameter name="cuttingTimeMapRefresh" type="integer" min="1">
-                               <label>Cutting Time/Map Refresh Interval</label>
-                               <description>The number of minutes between refreshing last/next cutting time and map.</description>
+                       <parameter name="cuttingTimeRefresh" type="integer" min="1">
+                               <label>Cutting Time Refresh Interval</label>
+                               <description>The number of minutes between refreshing last/next cutting time.</description>
                                <advanced>true</advanced>
                                <default>60</default>
                        </parameter>