]> git.basschouten.com Git - openhab-addons.git/blob
af035c3a7ddfb15067bee8eae0599b46b96bdcc3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.boschindego.internal.handler;
14
15 import static org.openhab.binding.boschindego.internal.BoschIndegoBindingConstants.*;
16 import static org.openhab.binding.boschindego.internal.IndegoStateConstants.*;
17
18 import java.math.BigDecimal;
19 import java.util.LinkedList;
20 import java.util.Map;
21 import java.util.Queue;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
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;
38
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;
45
46 /**
47  * The {@link BoschIndegoHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author Jonas Fleck - Initial contribution
51  */
52 public class BoschIndegoHandler extends BaseThingHandler {
53
54     private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
55     private final Queue<DeviceCommand> commandQueue = new LinkedList<>();
56
57     private ScheduledFuture<?> pollFuture;
58
59     // If false the request is already scheduled.
60     private boolean shouldReschedule;
61
62     public BoschIndegoHandler(Thing thing) {
63         super(thing);
64     }
65
66     @Override
67     public void handleCommand(ChannelUID channelUID, Command command) {
68         if (command instanceof RefreshType) {
69             // Currently manual refreshing is not possible in the moment
70             return;
71         } else if (channelUID.getId().equals(STATE) && command instanceof DecimalType) {
72             if (command instanceof DecimalType) {
73                 sendCommand(((DecimalType) command).intValue());
74             }
75         }
76     }
77
78     private void sendCommand(int commandInt) {
79         DeviceCommand command;
80         switch (commandInt) {
81             case 1:
82                 command = DeviceCommand.MOW;
83                 break;
84             case 2:
85                 command = DeviceCommand.RETURN;
86                 break;
87             case 3:
88                 command = DeviceCommand.PAUSE;
89                 break;
90             default:
91                 logger.error("Invalid command");
92                 return;
93         }
94         synchronized (commandQueue) {
95             // Add command to queue to avoid blocking
96             commandQueue.offer(command);
97             if (shouldReschedule) {
98                 shouldReschedule = false;
99                 reschedule();
100             }
101         }
102     }
103
104     private synchronized void poll() {
105         // Create controller instance
106         try {
107             IndegoController controller = new IndegoController(getConfig().get("username").toString(),
108                     getConfig().get("password").toString());
109             // Connect to server
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();
124                 }
125                 // For newer commands a new request is needed
126                 shouldReschedule = true;
127             }
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);
133                 try {
134                     for (int i = 0; i < 30 && !Thread.interrupted(); i++) {
135                         DeviceStateInformation stateTmp = controller.getState();
136                         if (state.getState() != stateTmp.getState()) {
137                             state = stateTmp;
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());
144                             break;
145                         }
146                         Thread.sleep(1000);
147                     }
148
149                 } catch (InterruptedException e) {
150                     // Nothing to do here
151                 }
152             }
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()));
161
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);
169         }
170     }
171
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;
176     }
177
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.");
182             return false;
183         }
184
185         // Command is equal to current state
186         if (command == state) {
187             logger.debug("Command is equal to state");
188             return false;
189         }
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");
193             return false;
194         }
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");
198             return false;
199         }
200         return true;
201     }
202
203     private int getStatusFromCommand(DeviceCommand command) {
204         int status;
205         switch (command) {
206             case MOW:
207                 status = 1;
208                 break;
209             case RETURN:
210                 status = 2;
211                 break;
212             case PAUSE:
213                 status = 3;
214                 break;
215             default:
216                 status = 0;
217         }
218         return status;
219     }
220
221     @Override
222     public void dispose() {
223         super.dispose();
224         logger.debug("removing thing..");
225         if (pollFuture != null) {
226             pollFuture.cancel(true);
227         }
228     }
229
230     private void reschedule() {
231         logger.debug("rescheduling");
232
233         if (pollFuture != null) {
234             pollFuture.cancel(false);
235         }
236
237         int refreshRate = ((BigDecimal) getConfig().get("refresh")).intValue();
238         pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshRate, TimeUnit.SECONDS);
239     }
240
241     @Override
242     public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
243         super.handleConfigurationUpdate(configurationParameters);
244         reschedule();
245     }
246
247     @Override
248     public void initialize() {
249         updateStatus(ThingStatus.OFFLINE);
250         reschedule();
251     }
252 }