]> git.basschouten.com Git - openhab-addons.git/blob
868e1be56f03a38312aa9767a2d1d63dfe98fea3
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
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.DeviceStatus;
24 import org.openhab.binding.boschindego.internal.IndegoController;
25 import org.openhab.binding.boschindego.internal.config.BoschIndegoConfiguration;
26 import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
27 import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
28 import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
29 import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.UnDefType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * The {@link BoschIndegoHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Jonas Fleck - Initial contribution
49  * @author Jacob Laursen - Refactoring, bugfixing and removal of dependency towards abandoned library
50  */
51 @NonNullByDefault
52 public class BoschIndegoHandler extends BaseThingHandler {
53
54     private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
55     private final HttpClient httpClient;
56
57     private @NonNullByDefault({}) IndegoController controller;
58     private @Nullable ScheduledFuture<?> pollFuture;
59     private long refreshRate;
60     private boolean propertiesInitialized;
61
62     public BoschIndegoHandler(Thing thing, HttpClient httpClient) {
63         super(thing);
64         this.httpClient = httpClient;
65     }
66
67     @Override
68     public void initialize() {
69         logger.debug("Initializing Indego handler");
70         BoschIndegoConfiguration config = getConfigAs(BoschIndegoConfiguration.class);
71         String username = config.username;
72         String password = config.password;
73
74         if (username == null || username.isBlank()) {
75             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
76                     "@text/offline.conf-error.missing-username");
77             return;
78         }
79         if (password == null || password.isBlank()) {
80             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
81                     "@text/offline.conf-error.missing-password");
82             return;
83         }
84
85         controller = new IndegoController(httpClient, username, password);
86         refreshRate = config.refresh;
87
88         updateStatus(ThingStatus.UNKNOWN);
89         this.pollFuture = scheduler.scheduleWithFixedDelay(this::refreshState, 0, refreshRate, TimeUnit.SECONDS);
90     }
91
92     @Override
93     public void dispose() {
94         logger.debug("Disposing Indego handler");
95         ScheduledFuture<?> pollFuture = this.pollFuture;
96         if (pollFuture != null) {
97             pollFuture.cancel(true);
98         }
99         this.pollFuture = null;
100     }
101
102     @Override
103     public void handleCommand(ChannelUID channelUID, Command command) {
104         if (command == RefreshType.REFRESH) {
105             scheduler.submit(() -> this.refreshState());
106             return;
107         }
108         try {
109             if (command instanceof DecimalType && channelUID.getId().equals(STATE)) {
110                 sendCommand(((DecimalType) command).intValue());
111             }
112         } catch (IndegoAuthenticationException e) {
113             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
114                     "@text/offline.comm-error.authentication-failure");
115         } catch (IndegoException e) {
116             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
117         }
118     }
119
120     private void sendCommand(int commandInt) throws IndegoException {
121         DeviceCommand command;
122         switch (commandInt) {
123             case 1:
124                 command = DeviceCommand.MOW;
125                 break;
126             case 2:
127                 command = DeviceCommand.RETURN;
128                 break;
129             case 3:
130                 command = DeviceCommand.PAUSE;
131                 break;
132             default:
133                 logger.warn("Invalid command {}", commandInt);
134                 return;
135         }
136
137         DeviceStateResponse state = controller.getState();
138         DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
139         if (!verifyCommand(command, deviceStatus, state.error)) {
140             return;
141         }
142         logger.debug("Sending command {}", command);
143         updateState(TEXTUAL_STATE, UnDefType.UNDEF);
144         controller.sendCommand(command);
145         state = controller.getState();
146         updateStatus(ThingStatus.ONLINE);
147         updateState(state);
148     }
149
150     private void refreshState() {
151         try {
152             if (!propertiesInitialized) {
153                 getThing().setProperty(Thing.PROPERTY_SERIAL_NUMBER, controller.getSerialNumber());
154                 propertiesInitialized = true;
155             }
156
157             DeviceStateResponse state = controller.getState();
158             updateStatus(ThingStatus.ONLINE);
159             updateState(state);
160         } catch (IndegoAuthenticationException e) {
161             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
162                     "@text/offline.comm-error.authentication-failure");
163         } catch (IndegoException e) {
164             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
165         }
166     }
167
168     private void updateState(DeviceStateResponse state) {
169         DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
170         int status = getStatusFromCommand(deviceStatus.getAssociatedCommand());
171         int mowed = state.mowed;
172         int error = state.error;
173         int statecode = state.state;
174         boolean ready = isReadyToMow(deviceStatus, state.error);
175
176         updateState(STATECODE, new DecimalType(statecode));
177         updateState(READY, new DecimalType(ready ? 1 : 0));
178         updateState(ERRORCODE, new DecimalType(error));
179         updateState(MOWED, new PercentType(mowed));
180         updateState(STATE, new DecimalType(status));
181         updateState(TEXTUAL_STATE, new StringType(deviceStatus.getMessage()));
182     }
183
184     private boolean isReadyToMow(DeviceStatus deviceStatus, int error) {
185         return deviceStatus.isReadyToMow() && error == 0;
186     }
187
188     private boolean verifyCommand(DeviceCommand command, DeviceStatus deviceStatus, int errorCode) {
189         // Mower reported an error
190         if (errorCode != 0) {
191             logger.error("The mower reported an error.");
192             return false;
193         }
194
195         // Command is equal to current state
196         if (command == deviceStatus.getAssociatedCommand()) {
197             logger.debug("Command is equal to state");
198             return false;
199         }
200         // Cant pause while the mower is docked
201         if (command == DeviceCommand.PAUSE && deviceStatus.getAssociatedCommand() == DeviceCommand.RETURN) {
202             logger.debug("Can't pause the mower while it's docked or docking");
203             return false;
204         }
205         // Command means "MOW" but mower is not ready
206         if (command == DeviceCommand.MOW && !isReadyToMow(deviceStatus, errorCode)) {
207             logger.debug("The mower is not ready to mow at the moment");
208             return false;
209         }
210         return true;
211     }
212
213     private int getStatusFromCommand(@Nullable DeviceCommand command) {
214         if (command == null) {
215             return 0;
216         }
217         int status;
218         switch (command) {
219             case MOW:
220                 status = 1;
221                 break;
222             case RETURN:
223                 status = 2;
224                 break;
225             case PAUSE:
226                 status = 3;
227                 break;
228             default:
229                 status = 0;
230         }
231         return status;
232     }
233 }