[automower] Add planner, calendar and command channels (#8802)
* [Automower] Enhanced binding: - Added support for the planner and calendar data - Added command channels - Updated docs Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com> * [Automower] Fixed consts with channel ids after removal of channel groups. Improved the mower state update: - Cache the last read state from API - Use cached mower state so the items linked will always be up to date without the need to wait for API refresh period. - Use timeZoneProvider to user user set timezone. Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com> * Rolledback NotNullByDefault annotation Signed-off-by: Marcin Czeczko <marcin.czeczko@gmail.com>
This commit is contained in:
@@ -20,10 +20,10 @@ import org.openhab.core.thing.ThingTypeUID;
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Markus Pfleger - Initial contribution
|
||||
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AutomowerBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "automower";
|
||||
|
||||
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
@@ -32,7 +32,7 @@ public class AutomowerBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_AUTOMOWER = new ThingTypeUID(BINDING_ID, "automower");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_MOWER_NAME = "name";
|
||||
public static final String CHANNEL_STATUS_NAME = "name";
|
||||
public static final String CHANNEL_STATUS_MODE = "mode";
|
||||
public static final String CHANNEL_STATUS_ACTIVITY = "activity";
|
||||
public static final String CHANNEL_STATUS_STATE = "state";
|
||||
@@ -40,6 +40,17 @@ public class AutomowerBindingConstants {
|
||||
public static final String CHANNEL_STATUS_BATTERY = "battery";
|
||||
public static final String CHANNEL_STATUS_ERROR_CODE = "error-code";
|
||||
public static final String CHANNEL_STATUS_ERROR_TIMESTAMP = "error-timestamp";
|
||||
public static final String CHANNEL_PLANNER_NEXT_START = "planner-next-start";
|
||||
public static final String CHANNEL_PLANNER_OVERRIDE_ACTION = "planner-override-action";
|
||||
public static final String CHANNEL_CALENDAR_TASKS = "calendar-tasks";
|
||||
|
||||
// Command channels
|
||||
public static final String CHANNEL_COMMAND_START = "start";
|
||||
public static final String CHANNEL_COMMAND_RESUME_SCHEDULE = "resume_schedule";
|
||||
public static final String CHANNEL_COMMAND_PAUSE = "pause";
|
||||
public static final String CHANNEL_COMMAND_PARK = "park";
|
||||
public static final String CHANNEL_COMMAND_PARK_UNTIL_NEXT_SCHEDULE = "park_until_next_schedule";
|
||||
public static final String CHANNEL_COMMAND_PARK_UNTIL_NOTICE = "park_until_further_notice";
|
||||
|
||||
// Automower properties
|
||||
public static final String AUTOMOWER_ID = "mowerId";
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.openhab.binding.automower.internal.discovery.AutomowerDiscoveryServic
|
||||
import org.openhab.binding.automower.internal.things.AutomowerHandler;
|
||||
import org.openhab.core.auth.client.oauth2.OAuthFactory;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
@@ -55,12 +56,14 @@ public class AutomowerHandlerFactory extends BaseThingHandlerFactory {
|
||||
private final OAuthFactory oAuthFactory;
|
||||
protected final @NonNullByDefault({}) HttpClient httpClient;
|
||||
private @Nullable ServiceRegistration<?> automowerDiscoveryServiceRegistration;
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
@Activate
|
||||
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory,
|
||||
@Reference HttpClientFactory httpClientFactory) {
|
||||
public AutomowerHandlerFactory(@Reference OAuthFactory oAuthFactory, @Reference HttpClientFactory httpClientFactory,
|
||||
@Reference TimeZoneProvider timeZoneProvider) {
|
||||
this.oAuthFactory = oAuthFactory;
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,7 +80,7 @@ public class AutomowerHandlerFactory extends BaseThingHandlerFactory {
|
||||
}
|
||||
|
||||
if (AutomowerHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
|
||||
return new AutomowerHandler(thing);
|
||||
return new AutomowerHandler(thing, timeZoneProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.openhab.core.auth.client.oauth2.OAuthResponseException;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AutomowerBridge {
|
||||
|
||||
private final OAuthClientService authService;
|
||||
private final String appKey;
|
||||
private final String userName;
|
||||
@@ -96,7 +95,6 @@ public class AutomowerBridge {
|
||||
*/
|
||||
public void sendAutomowerCommand(String id, AutomowerCommand command, long commandDuration)
|
||||
throws AutomowerCommunicationException {
|
||||
|
||||
MowerCommandAttributes attributes = new MowerCommandAttributes();
|
||||
attributes.setDuration(commandDuration);
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.gson.GsonBuilder;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class HusqvarnaApi {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
protected final Gson gson;
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import com.google.gson.JsonSyntaxException;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AutomowerConnectApi extends HusqvarnaApi {
|
||||
|
||||
public AutomowerConnectApi(HttpClient httpClient) {
|
||||
super(httpClient);
|
||||
}
|
||||
@@ -81,7 +80,6 @@ public class AutomowerConnectApi extends HusqvarnaApi {
|
||||
|
||||
private ContentResponse executeRequest(String appKey, String token, final Request request)
|
||||
throws AutomowerCommunicationException {
|
||||
|
||||
request.timeout(10, TimeUnit.SECONDS);
|
||||
|
||||
request.header("Authorization-Provider", "husqvarna");
|
||||
|
||||
@@ -12,9 +12,17 @@
|
||||
*/
|
||||
package org.openhab.binding.automower.internal.rest.api.automowerconnect.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Markus Pfleger - Initial contribution
|
||||
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||
*/
|
||||
public class Calendar {
|
||||
private List<CalendarTask> tasks = new ArrayList<>();
|
||||
|
||||
public List<CalendarTask> getTasks() {
|
||||
return tasks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.automower.internal.rest.api.automowerconnect.dto;
|
||||
|
||||
/**
|
||||
* @author Marcin Czeczko - Initial contribution
|
||||
*/
|
||||
public class CalendarTask {
|
||||
/**
|
||||
* Start time expressed in minutes after midnight.
|
||||
*/
|
||||
private Integer start;
|
||||
|
||||
/**
|
||||
* Duration time expressed in minutes
|
||||
*/
|
||||
private Integer duration;
|
||||
private Boolean monday;
|
||||
private Boolean tuesday;
|
||||
private Boolean wednesday;
|
||||
private Boolean thursday;
|
||||
private Boolean friday;
|
||||
private Boolean saturday;
|
||||
private Boolean sunday;
|
||||
|
||||
public Integer getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public Integer getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public Boolean getMonday() {
|
||||
return monday;
|
||||
}
|
||||
|
||||
public Boolean getTuesday() {
|
||||
return tuesday;
|
||||
}
|
||||
|
||||
public Boolean getWednesday() {
|
||||
return wednesday;
|
||||
}
|
||||
|
||||
public Boolean getThursday() {
|
||||
return thursday;
|
||||
}
|
||||
|
||||
public Boolean getFriday() {
|
||||
return friday;
|
||||
}
|
||||
|
||||
public Boolean getSaturday() {
|
||||
return saturday;
|
||||
}
|
||||
|
||||
public Boolean getSunday() {
|
||||
return sunday;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,37 @@ package org.openhab.binding.automower.internal.rest.api.automowerconnect.dto;
|
||||
|
||||
/**
|
||||
* @author Markus Pfleger - Initial contribution
|
||||
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||
*/
|
||||
public class Planner {
|
||||
private long nextStartTimestamp;
|
||||
private RestrictedReason restrictedReason;
|
||||
private PlannerOverride override;
|
||||
|
||||
public long getNextStartTimestamp() {
|
||||
return nextStartTimestamp;
|
||||
}
|
||||
|
||||
public Planner setNextStartTimestamp(long nextStartTimestamp) {
|
||||
this.nextStartTimestamp = nextStartTimestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RestrictedReason getRestrictedReason() {
|
||||
return restrictedReason;
|
||||
}
|
||||
|
||||
public Planner setRestrictedReason(RestrictedReason restrictedReason) {
|
||||
this.restrictedReason = restrictedReason;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PlannerOverride getOverride() {
|
||||
return override;
|
||||
}
|
||||
|
||||
public Planner setOverride(PlannerOverride override) {
|
||||
this.override = override;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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.automower.internal.rest.api.automowerconnect.dto;
|
||||
|
||||
/**
|
||||
* @author Marcin Czeczko - Initial contribution
|
||||
*/
|
||||
public class PlannerOverride {
|
||||
private String action;
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public PlannerOverride setAction(String action) {
|
||||
this.action = action;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.automower.internal.rest.api.automowerconnect.dto;
|
||||
|
||||
/**
|
||||
* @author Marcin Czeczko - Initial Contribution
|
||||
*/
|
||||
public enum RestrictedReason {
|
||||
NONE,
|
||||
WEEK_SCHEDULE,
|
||||
PARK_OVERRIDE,
|
||||
SENSOR,
|
||||
DAILY_LIMIT,
|
||||
NOT_APPLICABLE
|
||||
}
|
||||
@@ -12,25 +12,47 @@
|
||||
*/
|
||||
package org.openhab.binding.automower.internal.things;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
|
||||
/**
|
||||
* @author Markus Pfleger - Initial contribution
|
||||
*/
|
||||
public enum AutomowerCommand {
|
||||
START("Start", "mower#start"),
|
||||
RESUME_SCHEDULE("ResumeSchedule", "mower#resume_schedule"),
|
||||
PAUSE("Pause", "mower#pause"),
|
||||
PARK("Park", "mower#park"),
|
||||
PARK_UNTIL_NEXT_SCHEDULE("ParkUntilNextSchedule", "mower#park_until_next_schedule"),
|
||||
PARK_UNTIL_FURTHER_NOTICE("ParkUntilFurtherNotice", "mower#park_until_further_notice");
|
||||
|
||||
START("Start"),
|
||||
RESUME_SCHEDULE("ResumeSchedule"),
|
||||
PAUSE("Pause"),
|
||||
PARK("Park"),
|
||||
PARK_UNTIL_NEXT_SCHEDULE("ParkUntilNextSchedule"),
|
||||
PARK_UNTIL_FURTHER_NOTICE("ParkUntilFurtherNotice");
|
||||
private static final Map<String, AutomowerCommand> CHANNEL_TO_CMD_MAP = new HashMap<>();
|
||||
|
||||
static {
|
||||
EnumSet.allOf(AutomowerCommand.class).forEach(cmd -> CHANNEL_TO_CMD_MAP.put(cmd.getChannel(), cmd));
|
||||
}
|
||||
|
||||
private final String command;
|
||||
private final String channel;
|
||||
|
||||
private AutomowerCommand(String command) {
|
||||
AutomowerCommand(String command, String channel) {
|
||||
this.command = command;
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public static Optional<AutomowerCommand> fromChannelUID(ChannelUID channelUID) {
|
||||
return Optional.ofNullable(CHANNEL_TO_CMD_MAP.get(channelUID.getId()));
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AutomowerConfiguration {
|
||||
|
||||
public @Nullable String mowerId;
|
||||
public @Nullable Integer pollingInterval;
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@ package org.openhab.binding.automower.internal.things;
|
||||
|
||||
import static org.openhab.binding.automower.internal.AutomowerBindingConstants.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -34,7 +33,10 @@ import org.openhab.binding.automower.internal.actions.AutomowerActions;
|
||||
import org.openhab.binding.automower.internal.bridge.AutomowerBridge;
|
||||
import org.openhab.binding.automower.internal.bridge.AutomowerBridgeHandler;
|
||||
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.Mower;
|
||||
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.RestrictedReason;
|
||||
import org.openhab.binding.automower.internal.rest.api.automowerconnect.dto.State;
|
||||
import org.openhab.binding.automower.internal.rest.exceptions.AutomowerCommunicationException;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@@ -51,14 +53,19 @@ import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* The {@link AutomowerHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Markus Pfleger - Initial contribution
|
||||
* @author Marcin Czeczko - Added support for planner & calendar data
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AutomowerHandler extends BaseThingHandler {
|
||||
@@ -68,12 +75,18 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
private static final long DEFAULT_POLLING_INTERVAL_S = TimeUnit.MINUTES.toSeconds(10);
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AutomowerHandler.class);
|
||||
private final TimeZoneProvider timeZoneProvider;
|
||||
|
||||
private AtomicReference<String> automowerId = new AtomicReference<String>(NO_ID);
|
||||
private long lastQueryTimeMs = 0L;
|
||||
|
||||
private @Nullable ScheduledFuture<?> automowerPollingJob;
|
||||
private long maxQueryFrequencyNanos = TimeUnit.MINUTES.toNanos(1);
|
||||
|
||||
private @Nullable Mower mowerState;
|
||||
|
||||
private Gson gson = new Gson();
|
||||
|
||||
private Runnable automowerPollingRunnable = () -> {
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
@@ -83,17 +96,32 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
}
|
||||
};
|
||||
|
||||
public AutomowerHandler(Thing thing) {
|
||||
public AutomowerHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
|
||||
super(thing);
|
||||
this.timeZoneProvider = timeZoneProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (RefreshType.REFRESH == command) {
|
||||
logger.debug("Refreshing channel '{}'", channelUID);
|
||||
refreshChannels(channelUID);
|
||||
} else {
|
||||
AutomowerCommand.fromChannelUID(channelUID).ifPresent(commandName -> {
|
||||
logger.debug("Sending command '{}'", commandName);
|
||||
getCommandValue(command).ifPresentOrElse(duration -> sendAutomowerCommand(commandName, duration),
|
||||
() -> sendAutomowerCommand(commandName));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Integer> getCommandValue(Type type) {
|
||||
if (type instanceof DecimalType) {
|
||||
return Optional.of(((DecimalType) type).intValue());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void refreshChannels(ChannelUID channelUID) {
|
||||
updateAutomowerState();
|
||||
}
|
||||
@@ -121,7 +149,6 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
automowerId.set(configMowerId);
|
||||
startAutomowerPolling(pollingIntervalS);
|
||||
}
|
||||
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||
}
|
||||
@@ -163,49 +190,47 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidResult(Mower mower) {
|
||||
return mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||
private boolean isValidResult(@Nullable Mower mower) {
|
||||
return mower != null && mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||
&& mower.getAttributes().getBattery() != null && mower.getAttributes().getSystem() != null;
|
||||
}
|
||||
|
||||
private boolean isConnected(Mower mower) {
|
||||
return mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||
private boolean isConnected(@Nullable Mower mower) {
|
||||
return mower != null && mower.getAttributes() != null && mower.getAttributes().getMetadata() != null
|
||||
&& mower.getAttributes().getMetadata().isConnected();
|
||||
}
|
||||
|
||||
private synchronized void updateAutomowerState() {
|
||||
if (System.nanoTime() - lastQueryTimeMs > maxQueryFrequencyNanos) {
|
||||
lastQueryTimeMs = System.nanoTime();
|
||||
String id = automowerId.get();
|
||||
try {
|
||||
AutomowerBridge automowerBridge = getAutomowerBridge();
|
||||
if (automowerBridge != null) {
|
||||
Mower mower = automowerBridge.getAutomowerStatus(id);
|
||||
String id = automowerId.get();
|
||||
try {
|
||||
AutomowerBridge automowerBridge = getAutomowerBridge();
|
||||
if (automowerBridge != null) {
|
||||
if (mowerState == null || (System.nanoTime() - lastQueryTimeMs > maxQueryFrequencyNanos)) {
|
||||
lastQueryTimeMs = System.nanoTime();
|
||||
mowerState = automowerBridge.getAutomowerStatus(id);
|
||||
}
|
||||
if (isValidResult(mowerState)) {
|
||||
initializeProperties(mowerState);
|
||||
|
||||
if (isValidResult(mower)) {
|
||||
initializeProperties(mower);
|
||||
updateChannelState(mowerState);
|
||||
|
||||
updateChannelState(mower);
|
||||
|
||||
if (isConnected(mower)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/comm-error-mower-not-connected-to-cloud");
|
||||
}
|
||||
if (isConnected(mowerState)) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/comm-error-query-mower-failed");
|
||||
"@text/comm-error-mower-not-connected-to-cloud");
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/conf-error-no-bridge");
|
||||
"@text/comm-error-query-mower-failed");
|
||||
}
|
||||
} catch (AutomowerCommunicationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/comm-error-query-mower-failed");
|
||||
logger.warn("Unable to query automower status for: {}. Error: {}", id, e.getMessage());
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/conf-error-no-bridge");
|
||||
}
|
||||
} catch (AutomowerCommunicationException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/comm-error-query-mower-failed");
|
||||
logger.warn("Unable to query automower status for: {}. Error: {}", id, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +253,7 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
* "Park" commands
|
||||
*/
|
||||
public void sendAutomowerCommand(AutomowerCommand command, long commandDurationMinutes) {
|
||||
logger.debug("Sending command '{} {}'", command.getCommand(), commandDurationMinutes);
|
||||
String id = automowerId.get();
|
||||
try {
|
||||
AutomowerBridge automowerBridge = getAutomowerBridge();
|
||||
@@ -243,30 +269,53 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
updateAutomowerState();
|
||||
}
|
||||
|
||||
private void updateChannelState(Mower mower) {
|
||||
if (isValidResult(mower)) {
|
||||
updateState(CHANNEL_MOWER_NAME, new StringType(mower.getAttributes().getSystem().getName()));
|
||||
private String restrictedState(RestrictedReason reason) {
|
||||
return "RESTRICTED_" + reason.name();
|
||||
}
|
||||
|
||||
private void updateChannelState(@Nullable Mower mower) {
|
||||
if (isValidResult(mower)) {
|
||||
updateState(CHANNEL_STATUS_NAME, new StringType(mower.getAttributes().getSystem().getName()));
|
||||
updateState(CHANNEL_STATUS_MODE, new StringType(mower.getAttributes().getMower().getMode().name()));
|
||||
updateState(CHANNEL_STATUS_ACTIVITY, new StringType(mower.getAttributes().getMower().getActivity().name()));
|
||||
updateState(CHANNEL_STATUS_STATE, new StringType(mower.getAttributes().getMower().getState().name()));
|
||||
|
||||
Instant statusTimestamp = Instant.ofEpochMilli(mower.getAttributes().getMetadata().getStatusTimestamp());
|
||||
if (mower.getAttributes().getMower().getState() != State.RESTRICTED) {
|
||||
updateState(CHANNEL_STATUS_STATE, new StringType(mower.getAttributes().getMower().getState().name()));
|
||||
} else {
|
||||
updateState(CHANNEL_STATUS_STATE,
|
||||
new StringType(restrictedState(mower.getAttributes().getPlanner().getRestrictedReason())));
|
||||
}
|
||||
|
||||
updateState(CHANNEL_STATUS_LAST_UPDATE,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(statusTimestamp, ZoneId.systemDefault())));
|
||||
new DateTimeType(toZonedDateTime(mower.getAttributes().getMetadata().getStatusTimestamp())));
|
||||
updateState(CHANNEL_STATUS_BATTERY, new QuantityType<Dimensionless>(
|
||||
mower.getAttributes().getBattery().getBatteryPercent(), Units.PERCENT));
|
||||
|
||||
updateState(CHANNEL_STATUS_ERROR_CODE, new DecimalType(mower.getAttributes().getMower().getErrorCode()));
|
||||
|
||||
Instant errorCodeTimestamp = Instant.ofEpochMilli(mower.getAttributes().getMower().getErrorCodeTimestamp());
|
||||
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(errorCodeTimestamp, ZoneId.systemDefault())));
|
||||
long errorCodeTimestamp = mower.getAttributes().getMower().getErrorCodeTimestamp();
|
||||
if (errorCodeTimestamp == 0L) {
|
||||
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP, UnDefType.NULL);
|
||||
} else {
|
||||
updateState(CHANNEL_STATUS_ERROR_TIMESTAMP, new DateTimeType(toZonedDateTime(errorCodeTimestamp)));
|
||||
}
|
||||
|
||||
long nextStartTimestamp = mower.getAttributes().getPlanner().getNextStartTimestamp();
|
||||
// If next start timestamp is 0 it means the mower should start now, so using current timestamp
|
||||
if (nextStartTimestamp == 0L) {
|
||||
updateState(CHANNEL_PLANNER_NEXT_START, UnDefType.NULL);
|
||||
} else {
|
||||
updateState(CHANNEL_PLANNER_NEXT_START, new DateTimeType(toZonedDateTime(nextStartTimestamp)));
|
||||
}
|
||||
updateState(CHANNEL_PLANNER_OVERRIDE_ACTION,
|
||||
new StringType(mower.getAttributes().getPlanner().getOverride().getAction()));
|
||||
|
||||
updateState(CHANNEL_CALENDAR_TASKS,
|
||||
new StringType(gson.toJson(mower.getAttributes().getCalendar().getTasks())));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeProperties(Mower mower) {
|
||||
private void initializeProperties(@Nullable Mower mower) {
|
||||
Map<String, String> properties = editProperties();
|
||||
|
||||
properties.put(AutomowerBindingConstants.AUTOMOWER_ID, mower.getId());
|
||||
@@ -280,4 +329,18 @@ public class AutomowerHandler extends BaseThingHandler {
|
||||
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts timestamp returned by the Automower API into local time-zone.
|
||||
* Timestamp returned by the API doesn't have offset and it always in the current time zone - it can be treated as
|
||||
* UTC.
|
||||
* Method builds a ZonedDateTime with same hour value but in the current system timezone.
|
||||
*
|
||||
* @param timestamp - Automower API timestamp
|
||||
* @return ZonedDateTime in system timezone
|
||||
*/
|
||||
private ZonedDateTime toZonedDateTime(long timestamp) {
|
||||
Instant timestampInstant = Instant.ofEpochMilli(timestamp);
|
||||
return ZonedDateTime.ofInstant(timestampInstant, timeZoneProvider.getTimeZone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ thing-type.config.automower.automower.timestamp.label = Last State Update
|
||||
thing-type.config.automower.automower.batteryPct.label = Battery Percentage
|
||||
|
||||
# channel types
|
||||
channel-type.automower.name.label = Name
|
||||
channel-type.automower.name.description = Automower name
|
||||
channel-type.automower.mode.label = Mode
|
||||
channel-type.automower.mode.description = Mode
|
||||
channel-type.automower.activity.label = Activity
|
||||
@@ -23,7 +21,14 @@ channel-type.automower.state.label = State
|
||||
channel-type.automower.state.description = State
|
||||
channel-type.automower.last-update.label = Last Update
|
||||
channel-type.automower.last-update.description = Last Update
|
||||
|
||||
channel-type.automower.planner-next-start.label= Next planned start
|
||||
channel-type.automower.planner-next-start.description= Next planned start
|
||||
channel-type.automower.planner-override-action.label = Planner override action
|
||||
channel-type.automower.planner-override-action.description = Planner override action
|
||||
channel-type.automower.planner-restricted-reason.label = Planner restriction
|
||||
channel-type.automower.planner-restricted-reason.description = Planner restriction
|
||||
channel-type.automower.calendar-tasks.label = The information about the planner as JSON
|
||||
channel-type.automower.calendar-tasks.description = The information about the planner as JSON
|
||||
|
||||
conf-error-no-app-key = Cannot connect to Automower bridge as no app key is available in the configuration
|
||||
conf-error-no-username = Cannot connect to Automower bridge as no username is available in the configuration
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
<config-description>
|
||||
<parameter name="appKey" type="text" required="true">
|
||||
<label>Application Key</label>
|
||||
<description>The Application Key is required to communication with the Automower Connect Api. It can be obtained by
|
||||
registering an Application on the Husqvarna Website. This application also needs to be connected to the
|
||||
<description>The Application Key is required to communication with the Automower Connect Api at
|
||||
https://developer.husqvarnagroup.cloud/. It can be obtained by
|
||||
registering an Application on the Husqvarna Website.
|
||||
This application also needs to be connected to the
|
||||
"Authentication API" and the "Automower Connect API"</description>
|
||||
</parameter>
|
||||
<parameter name="userName" type="text" required="true">
|
||||
@@ -21,7 +23,7 @@
|
||||
<description>The user name for which the application key has been issued</description>
|
||||
</parameter>
|
||||
<parameter name="password" type="text" required="true">
|
||||
<context>Password</context>
|
||||
<context>password</context>
|
||||
<description>The password for the given user</description>
|
||||
</parameter>
|
||||
<parameter name="pollingInterval" type="integer" required="false" unit="s">
|
||||
@@ -48,9 +50,18 @@
|
||||
<channel id="activity" typeId="activityType"/>
|
||||
<channel id="state" typeId="stateType"/>
|
||||
<channel id="last-update" typeId="lastUpdateType"/>
|
||||
<channel id="battery" typeId="batteryType"/>
|
||||
<channel id="battery" typeId="system.battery-level"/>
|
||||
<channel id="error-code" typeId="errorCodeType"/>
|
||||
<channel id="error-timestamp" typeId="errorTimestampType"/>
|
||||
<channel id="planner-next-start" typeId="plannerNextStartTimestampType"/>
|
||||
<channel id="planner-override-action" typeId="plannerOverrideActionType"/>
|
||||
<channel id="calendar-tasks" typeId="calendarTasksType"/>
|
||||
<channel id="start" typeId="start"/>
|
||||
<channel id="resume_schedule" typeId="resumeSchedule"/>
|
||||
<channel id="pause" typeId="pause"/>
|
||||
<channel id="park" typeId="park"/>
|
||||
<channel id="park_until_next_schedule" typeId="parkUntilNextSchedule"/>
|
||||
<channel id="park_until_further_notice" typeId="parkUntilFurtherNotice"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
@@ -126,7 +137,11 @@
|
||||
<option value="IN_OPERATION">Working</option>
|
||||
<option value="WAIT_UPDATING">Downloading new firmware</option>
|
||||
<option value="WAIT_POWER_UP">Booting mower</option>
|
||||
<option value="RESTRICTED">Waiting</option>
|
||||
<option value="RESTRICTED_NONE">Waiting</option>
|
||||
<option value="RESTRICTED_WEEK_SCHEDULE">Restricted by week schedule</option>
|
||||
<option value="RESTRICTED_PARK_OVERRIDE">Forced to park</option>
|
||||
<option value="RESTRICTED_SENSOR">Restricted by sensor</option>
|
||||
<option value="RESTRICTED_DAILY_LIMIT">Restricted by daily limit</option>
|
||||
<option value="OFF">Off</option>
|
||||
<option value="STOPPED">Stopped- Manual intervention required</option>
|
||||
<option value="ERROR">Error</option>
|
||||
@@ -164,4 +179,64 @@
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="plannerNextStartTimestampType">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Next Auto Start</label>
|
||||
<description>The channel providing the time for the next auto start. If the mower is charging then the value is the
|
||||
estimated time when it will be leaving the charging station. If the mower is about to start now, the value is NULL.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="plannerOverrideActionType">
|
||||
<item-type>String</item-type>
|
||||
<label>Override Action</label>
|
||||
<description>The channel providing an action that overrides current planner operation.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="calendarTasksType">
|
||||
<item-type>String</item-type>
|
||||
<label>Planner Info JSON</label>
|
||||
<description>The channel providing a JSON with the information about the planner tasks.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="start">
|
||||
<item-type>Number</item-type>
|
||||
<label>Start with Duration</label>
|
||||
<description>Start for a duration in minutes</description>
|
||||
<state pattern="%d min" min="1" max="1440" step="1"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="resumeSchedule">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Resume Schedule</label>
|
||||
<description>Resume schedule</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pause">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Pause</label>
|
||||
<description>Pause the mower now until manual resume</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="park">
|
||||
<item-type>Number</item-type>
|
||||
<label>Park with Duration</label>
|
||||
<description>Park for a duration in minutes</description>
|
||||
<state pattern="%d min" min="1" max="1440" step="1"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="parkUntilNextSchedule">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Park Until Next Schedule</label>
|
||||
<description>Park until next schedule</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="parkUntilFurtherNotice">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Park and Pause the Schedule</label>
|
||||
<description>Park and pause the mower schedule until manual resume</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
||||
Reference in New Issue
Block a user