]> git.basschouten.com Git - openhab-addons.git/blob
c3d9afa3268167c61deb221814a64231e0a16d7f
[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.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;
44
45 /**
46  * The {@link BoschIndegoHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Jonas Fleck - Initial contribution
50  * @author Jacob Laursen - Refactoring, bugfixing and removal of dependency towards abandoned library
51  */
52 @NonNullByDefault
53 public class BoschIndegoHandler extends BaseThingHandler {
54
55     private final Logger logger = LoggerFactory.getLogger(BoschIndegoHandler.class);
56     private final HttpClient httpClient;
57     private final BoschIndegoTranslationProvider translationProvider;
58
59     private @NonNullByDefault({}) IndegoController controller;
60     private @Nullable ScheduledFuture<?> pollFuture;
61     private long refreshRate;
62     private boolean propertiesInitialized;
63
64     public BoschIndegoHandler(Thing thing, HttpClient httpClient, BoschIndegoTranslationProvider translationProvider) {
65         super(thing);
66         this.httpClient = httpClient;
67         this.translationProvider = translationProvider;
68     }
69
70     @Override
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;
76
77         if (username == null || username.isBlank()) {
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
79                     "@text/offline.conf-error.missing-username");
80             return;
81         }
82         if (password == null || password.isBlank()) {
83             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
84                     "@text/offline.conf-error.missing-password");
85             return;
86         }
87
88         controller = new IndegoController(httpClient, username, password);
89         refreshRate = config.refresh;
90
91         updateStatus(ThingStatus.UNKNOWN);
92         this.pollFuture = scheduler.scheduleWithFixedDelay(this::refreshState, 0, refreshRate, TimeUnit.SECONDS);
93     }
94
95     @Override
96     public void dispose() {
97         logger.debug("Disposing Indego handler");
98         ScheduledFuture<?> pollFuture = this.pollFuture;
99         if (pollFuture != null) {
100             pollFuture.cancel(true);
101         }
102         this.pollFuture = null;
103     }
104
105     @Override
106     public void handleCommand(ChannelUID channelUID, Command command) {
107         if (command == RefreshType.REFRESH) {
108             scheduler.submit(() -> this.refreshState());
109             return;
110         }
111         try {
112             if (command instanceof DecimalType && channelUID.getId().equals(STATE)) {
113                 sendCommand(((DecimalType) command).intValue());
114             }
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());
120         }
121     }
122
123     private void sendCommand(int commandInt) throws IndegoException {
124         DeviceCommand command;
125         switch (commandInt) {
126             case 1:
127                 command = DeviceCommand.MOW;
128                 break;
129             case 2:
130                 command = DeviceCommand.RETURN;
131                 break;
132             case 3:
133                 command = DeviceCommand.PAUSE;
134                 break;
135             default:
136                 logger.warn("Invalid command {}", commandInt);
137                 return;
138         }
139
140         DeviceStateResponse state = controller.getState();
141         DeviceStatus deviceStatus = DeviceStatus.fromCode(state.state);
142         if (!verifyCommand(command, deviceStatus, state.error)) {
143             return;
144         }
145         logger.debug("Sending command {}", command);
146         updateState(TEXTUAL_STATE, UnDefType.UNDEF);
147         controller.sendCommand(command);
148         state = controller.getState();
149         updateStatus(ThingStatus.ONLINE);
150         updateState(state);
151     }
152
153     private void refreshState() {
154         try {
155             if (!propertiesInitialized) {
156                 getThing().setProperty(Thing.PROPERTY_SERIAL_NUMBER, controller.getSerialNumber());
157                 propertiesInitialized = true;
158             }
159
160             DeviceStateResponse state = controller.getState();
161             updateStatus(ThingStatus.ONLINE);
162             updateState(state);
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());
168         }
169     }
170
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);
178
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)));
185     }
186
187     private boolean isReadyToMow(DeviceStatus deviceStatus, int error) {
188         return deviceStatus.isReadyToMow() && error == 0;
189     }
190
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.");
195             return false;
196         }
197
198         // Command is equal to current state
199         if (command == deviceStatus.getAssociatedCommand()) {
200             logger.debug("Command is equal to state");
201             return false;
202         }
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");
206             return false;
207         }
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");
211             return false;
212         }
213         return true;
214     }
215
216     private int getStatusFromCommand(@Nullable DeviceCommand command) {
217         if (command == null) {
218             return 0;
219         }
220         int status;
221         switch (command) {
222             case MOW:
223                 status = 1;
224                 break;
225             case RETURN:
226                 status = 2;
227                 break;
228             case PAUSE:
229                 status = 3;
230                 break;
231             default:
232                 status = 0;
233         }
234         return status;
235     }
236 }