mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[boschindego] Plot location on map (#13179)
* Plot location on map * Invalidate map when requested by service * Optimize update of raw map Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
3b8567bd9e
commit
6028533e8e
@ -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
|
||||
|
||||
|
@ -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)),
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user