2 * Copyright (c) 2010-2022 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.boschindego.internal.handler;
15 import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.openhab.binding.boschindego.internal.BoschIndegoTranslationProvider;
24 import org.openhab.binding.boschindego.internal.DeviceStatus;
25 import org.openhab.binding.boschindego.internal.IndegoController;
26 import org.openhab.binding.boschindego.internal.config.BoschIndegoConfiguration;
27 import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
28 import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
29 import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
30 import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
31 import org.openhab.core.library.types.DecimalType;
32 import org.openhab.core.library.types.PercentType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.binding.BaseThingHandler;
39 import org.openhab.core.types.Command;
40 import org.openhab.core.types.RefreshType;
41 import org.openhab.core.types.UnDefType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link BoschIndegoHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Jonas Fleck - Initial contribution
50 * @author Jacob Laursen - Refactoring, bugfixing and removal of dependency towards abandoned library
53 public class BoschIndegoHandler extends BaseThingHandler {
55 private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
56 private final HttpClient httpClient;
57 private final BoschIndegoTranslationProvider translationProvider;
59 private @NonNullByDefault({}) IndegoController controller;
60 private @Nullable ScheduledFuture<?> pollFuture;
61 private long refreshRate;
62 private boolean propertiesInitialized;
64 public BoschIndegoHandler(Thing thing, HttpClient httpClient, BoschIndegoTranslationProvider translationProvider) {
66 this.httpClient = httpClient;
67 this.translationProvider = translationProvider;
71 public void initialize() {
72 logger.debug("Initializing Indego handler");
73 BoschIndegoConfiguration config = getConfigAs(BoschIndegoConfiguration.class);
74 String username = config.username;
75 String password = config.password;
77 if (username == null || username.isBlank()) {
78 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
79 "@text/offline.conf-error.missing-username");
82 if (password == null || password.isBlank()) {
83 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
84 "@text/offline.conf-error.missing-password");
88 controller = new IndegoController(httpClient, username, password);
89 refreshRate = config.refresh;
91 updateStatus(ThingStatus.UNKNOWN);
92 this.pollFuture = scheduler.scheduleWithFixedDelay(this::refreshState, 0, refreshRate, TimeUnit.SECONDS);
96 public void dispose() {
97 logger.debug("Disposing Indego handler");
98 ScheduledFuture<?> pollFuture = this.pollFuture;
99 if (pollFuture != null) {
100 pollFuture.cancel(true);
102 this.pollFuture = null;
106 public void handleCommand(ChannelUID channelUID, Command command) {
107 if (command == RefreshType.REFRESH) {
108 scheduler.submit(() -> this.refreshState());
112 if (command instanceof DecimalType && channelUID.getId().equals(STATE)) {
113 sendCommand(((DecimalType) command).intValue());
115 } catch (IndegoAuthenticationException e) {
116 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
117 "@text/offline.comm-error.authentication-failure");
118 } catch (IndegoException e) {
119 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
123 private void sendCommand(int commandInt) throws IndegoException {
124 DeviceCommand command;
125 switch (commandInt) {
127 command = DeviceCommand.MOW;
130 command = DeviceCommand.RETURN;
133 command = DeviceCommand.PAUSE;
136 logger.warn("Invalid command {}", commandInt);
140 DeviceStateResponse state = controller.getState();
141 DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
142 if (!verifyCommand(command, deviceStatus, state.error)) {
145 logger.debug("Sending command {}", command);
146 updateState(TEXTUAL_STATE, UnDefType.UNDEF);
147 controller.sendCommand(command);
148 state = controller.getState();
149 updateStatus(ThingStatus.ONLINE);
153 private void refreshState() {
155 if (!propertiesInitialized) {
156 getThing().setProperty(Thing.PROPERTY_SERIAL_NUMBER, controller.getSerialNumber());
157 propertiesInitialized = true;
160 DeviceStateResponse state = controller.getState();
161 updateStatus(ThingStatus.ONLINE);
163 } catch (IndegoAuthenticationException e) {
164 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
165 "@text/offline.comm-error.authentication-failure");
166 } catch (IndegoException e) {
167 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
171 private void updateState(DeviceStateResponse state) {
172 DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
173 int status = getStatusFromCommand(deviceStatus.getAssociatedCommand());
174 int mowed = state.mowed;
175 int error = state.error;
176 int statecode = state.state;
177 boolean ready = isReadyToMow(deviceStatus, state.error);
179 updateState(STATECODE, new DecimalType(statecode));
180 updateState(READY, new DecimalType(ready ? 1 : 0));
181 updateState(ERRORCODE, new DecimalType(error));
182 updateState(MOWED, new PercentType(mowed));
183 updateState(STATE, new DecimalType(status));
184 updateState(TEXTUAL_STATE, new StringType(deviceStatus.getMessage(translationProvider)));
187 private boolean isReadyToMow(DeviceStatus deviceStatus, int error) {
188 return deviceStatus.isReadyToMow() && error == 0;
191 private boolean verifyCommand(DeviceCommand command, DeviceStatus deviceStatus, int errorCode) {
192 // Mower reported an error
193 if (errorCode != 0) {
194 logger.error("The mower reported an error.");
198 // Command is equal to current state
199 if (command == deviceStatus.getAssociatedCommand()) {
200 logger.debug("Command is equal to state");
203 // Cant pause while the mower is docked
204 if (command == DeviceCommand.PAUSE && deviceStatus.getAssociatedCommand() == DeviceCommand.RETURN) {
205 logger.debug("Can't pause the mower while it's docked or docking");
208 // Command means "MOW" but mower is not ready
209 if (command == DeviceCommand.MOW && !isReadyToMow(deviceStatus, errorCode)) {
210 logger.debug("The mower is not ready to mow at the moment");
216 private int getStatusFromCommand(@Nullable DeviceCommand command) {
217 if (command == null) {