2 * Copyright (c) 2010-2021 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.*;
16 import static org.openhab.binding.boschindego.internal.IndegoStateConstants.*;
18 import java.math.BigDecimal;
19 import java.util.LinkedList;
21 import java.util.Queue;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
25 import org.openhab.core.library.types.DecimalType;
26 import org.openhab.core.library.types.PercentType;
27 import org.openhab.core.library.types.StringType;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.binding.BaseThingHandler;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.RefreshType;
35 import org.openhab.core.types.UnDefType;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import de.zazaz.iot.bosch.indego.DeviceCommand;
40 import de.zazaz.iot.bosch.indego.DeviceStateInformation;
41 import de.zazaz.iot.bosch.indego.DeviceStatus;
42 import de.zazaz.iot.bosch.indego.IndegoAuthenticationException;
43 import de.zazaz.iot.bosch.indego.IndegoController;
44 import de.zazaz.iot.bosch.indego.IndegoException;
47 * The {@link BoschIndegoHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Jonas Fleck - Initial contribution
52 public class BoschIndegoHandler extends BaseThingHandler {
54 private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
55 private final Queue<DeviceCommand> commandQueue = new LinkedList<>();
57 private ScheduledFuture<?> pollFuture;
59 // If false the request is already scheduled.
60 private boolean shouldReschedule;
62 public BoschIndegoHandler(Thing thing) {
67 public void handleCommand(ChannelUID channelUID, Command command) {
68 if (command instanceof RefreshType) {
69 // Currently manual refreshing is not possible in the moment
71 } else if (channelUID.getId().equals(STATE) && command instanceof DecimalType) {
72 if (command instanceof DecimalType) {
73 sendCommand(((DecimalType) command).intValue());
78 private void sendCommand(int commandInt) {
79 DeviceCommand command;
82 command = DeviceCommand.MOW;
85 command = DeviceCommand.RETURN;
88 command = DeviceCommand.PAUSE;
91 logger.error("Invalid command");
94 synchronized (commandQueue) {
95 // Add command to queue to avoid blocking
96 commandQueue.offer(command);
97 if (shouldReschedule) {
98 shouldReschedule = false;
104 private synchronized void poll() {
105 // Create controller instance
107 IndegoController controller = new IndegoController(getConfig().get("username").toString(),
108 getConfig().get("password").toString());
110 controller.connect();
111 // Query the device state
112 DeviceStateInformation state = controller.getState();
113 DeviceStatus statusWithMessage = DeviceStatus.decodeStatusCode(state.getState());
114 int status = getStatusFromCommand(statusWithMessage.getAssociatedCommand());
115 int mowed = state.getMowed();
116 int error = state.getError();
117 int statecode = state.getState();
118 boolean ready = isReadyToMow(state.getState(), state.getError());
119 DeviceCommand commandToSend = null;
120 synchronized (commandQueue) {
121 // Discard older commands
122 while (!commandQueue.isEmpty()) {
123 commandToSend = commandQueue.poll();
125 // For newer commands a new request is needed
126 shouldReschedule = true;
128 if (commandToSend != null && verifyCommand(commandToSend, statusWithMessage.getAssociatedCommand(),
129 state.getState(), error)) {
130 logger.debug("Sending command...");
131 updateState(TEXTUAL_STATE, UnDefType.UNDEF);
132 controller.sendCommand(commandToSend);
134 for (int i = 0; i < 30 && !Thread.interrupted(); i++) {
135 DeviceStateInformation stateTmp = controller.getState();
136 if (state.getState() != stateTmp.getState()) {
138 statusWithMessage = DeviceStatus.decodeStatusCode(state.getState());
139 status = getStatusFromCommand(statusWithMessage.getAssociatedCommand());
140 mowed = state.getMowed();
141 error = state.getError();
142 statecode = state.getState();
143 ready = isReadyToMow(state.getState(), state.getError());
149 } catch (InterruptedException e) {
150 // Nothing to do here
153 controller.disconnect();
154 updateStatus(ThingStatus.ONLINE);
155 updateState(STATECODE, new DecimalType(statecode));
156 updateState(READY, new DecimalType(ready ? 1 : 0));
157 updateState(ERRORCODE, new DecimalType(error));
158 updateState(MOWED, new PercentType(mowed));
159 updateState(STATE, new DecimalType(status));
160 updateState(TEXTUAL_STATE, new StringType(statusWithMessage.getMessage()));
162 } catch (IndegoAuthenticationException e) {
163 String message = "The login credentials are wrong or another client connected to your Indego account";
164 logger.warn(message, e);
165 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
166 } catch (IndegoException e) {
167 logger.warn("An error occurred", e);
168 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
172 private boolean isReadyToMow(int statusCode, int error) {
173 // I don´t know why bosch uses different state codes for the same state.
174 return (statusCode == STATE_DOCKED_1 || statusCode == STATE_DOCKED_2 || statusCode == STATE_DOCKED_3
175 || statusCode == STATE_PAUSED || statusCode == STATE_IDLE_IN_LAWN) && error == 0;
178 private boolean verifyCommand(DeviceCommand command, DeviceCommand state, int statusCode, int errorCode) {
179 // Mower reported an error
180 if (errorCode != 0) {
181 logger.error("The mower reported an error.");
185 // Command is equal to current state
186 if (command == state) {
187 logger.debug("Command is equal to state");
190 // Cant pause while the mower is docked
191 if (command == DeviceCommand.PAUSE && state == DeviceCommand.RETURN) {
192 logger.debug("Can´t pause the mower while it´s docked or docking");
195 // Command means "MOW" but mower is not ready
196 if (command == DeviceCommand.MOW && !isReadyToMow(statusCode, errorCode)) {
197 logger.debug("The mower is not ready to mow in the moment");
203 private int getStatusFromCommand(DeviceCommand command) {
222 public void dispose() {
224 logger.debug("removing thing..");
225 if (pollFuture != null) {
226 pollFuture.cancel(true);
230 private void reschedule() {
231 logger.debug("rescheduling");
233 if (pollFuture != null) {
234 pollFuture.cancel(false);
237 int refreshRate = ((BigDecimal) getConfig().get("refresh")).intValue();
238 pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshRate, TimeUnit.SECONDS);
242 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
243 super.handleConfigurationUpdate(configurationParameters);
248 public void initialize() {
249 updateStatus(ThingStatus.OFFLINE);