]> git.basschouten.com Git - openhab-addons.git/blob
eb506b771667b5a47f7a838125a047d0572a56b4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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;
14
15 import java.time.Duration;
16 import java.time.Instant;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
20 import org.eclipse.jetty.client.HttpClient;
21 import org.eclipse.jetty.http.HttpStatus;
22 import org.openhab.binding.boschindego.internal.dto.DeviceCommand;
23 import org.openhab.binding.boschindego.internal.dto.PredictiveAdjustment;
24 import org.openhab.binding.boschindego.internal.dto.PredictiveStatus;
25 import org.openhab.binding.boschindego.internal.dto.request.SetStateRequest;
26 import org.openhab.binding.boschindego.internal.dto.response.DeviceCalendarResponse;
27 import org.openhab.binding.boschindego.internal.dto.response.DevicePropertiesResponse;
28 import org.openhab.binding.boschindego.internal.dto.response.DeviceStateResponse;
29 import org.openhab.binding.boschindego.internal.dto.response.LocationWeatherResponse;
30 import org.openhab.binding.boschindego.internal.dto.response.OperatingDataResponse;
31 import org.openhab.binding.boschindego.internal.dto.response.PredictiveLastCuttingResponse;
32 import org.openhab.binding.boschindego.internal.dto.response.PredictiveNextCuttingResponse;
33 import org.openhab.binding.boschindego.internal.exceptions.IndegoAuthenticationException;
34 import org.openhab.binding.boschindego.internal.exceptions.IndegoException;
35 import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidCommandException;
36 import org.openhab.binding.boschindego.internal.exceptions.IndegoInvalidResponseException;
37 import org.openhab.binding.boschindego.internal.exceptions.IndegoTimeoutException;
38 import org.openhab.core.auth.client.oauth2.OAuthClientService;
39 import org.openhab.core.library.types.RawType;
40
41 /**
42  * Controller for communicating with a Bosch Indego device through Bosch services.
43  * This class provides methods for retrieving state information as well as controlling
44  * the device.
45  * 
46  * The implementation is based on zazaz-de's iot-device-bosch-indego-controller, but
47  * rewritten from scratch to use Jetty HTTP client for HTTP communication and GSON for
48  * JSON parsing. Thanks to Oliver Schünemann for providing the original implementation.
49  * 
50  * @see <a href=
51  *      "https://github.com/zazaz-de/iot-device-bosch-indego-controller">zazaz-de/iot-device-bosch-indego-controller</a>
52  * 
53  * @author Jacob Laursen - Initial contribution
54  */
55 @NonNullByDefault
56 public class IndegoDeviceController extends IndegoController {
57
58     private String serialNumber;
59
60     /**
61      * Initialize the controller instance.
62      * 
63      * @param httpClient the HttpClient for communicating with the service
64      * @param oAuthClientService the OAuthClientService for authorization
65      * @param serialNumber the serial number of the device instance
66      */
67     public IndegoDeviceController(HttpClient httpClient, OAuthClientService oAuthClientService, String serialNumber) {
68         super(httpClient, oAuthClientService);
69         if (serialNumber.isBlank()) {
70             throw new IllegalArgumentException("Serial number must be provided");
71         }
72         this.serialNumber = serialNumber;
73     }
74
75     /**
76      * Queries the serial number and device service properties from the server.
77      *
78      * @return the device serial number and properties
79      * @throws IndegoAuthenticationException if request was rejected as unauthorized
80      * @throws IndegoException if any communication or parsing error occurred
81      */
82     public DevicePropertiesResponse getDeviceProperties() throws IndegoAuthenticationException, IndegoException {
83         return super.getDeviceProperties(serialNumber);
84     }
85
86     /**
87      * Queries the device state from the server.
88      * 
89      * @return the device state
90      * @throws IndegoAuthenticationException if request was rejected as unauthorized
91      * @throws IndegoException if any communication or parsing error occurred
92      */
93     public DeviceStateResponse getState() throws IndegoAuthenticationException, IndegoException {
94         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/state", DeviceStateResponse.class);
95     }
96
97     /**
98      * Queries the device state from the server. This overload will return when the state
99      * has changed, or the timeout has been reached.
100      * 
101      * @param timeout maximum time to wait for response
102      * @return the device state
103      * @throws IndegoAuthenticationException if request was rejected as unauthorized
104      * @throws IndegoException if any communication or parsing error occurred
105      */
106     public DeviceStateResponse getState(Duration timeout) throws IndegoAuthenticationException, IndegoException {
107         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/state?longpoll=true&timeout=" + timeout.getSeconds(),
108                 DeviceStateResponse.class);
109     }
110
111     /**
112      * Queries the device operating data from the server.
113      * Server will request this directly from the device, so operation might be slow.
114      * 
115      * @return the device state
116      * @throws IndegoAuthenticationException if request was rejected as unauthorized
117      * @throws IndegoTimeoutException if device cannot be reached (gateway timeout error)
118      * @throws IndegoException if any communication or parsing error occurred
119      */
120     public OperatingDataResponse getOperatingData()
121             throws IndegoAuthenticationException, IndegoTimeoutException, IndegoException {
122         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/operatingData", OperatingDataResponse.class);
123     }
124
125     /**
126      * Queries the map generated by the device from the server.
127      * 
128      * @return the garden map
129      * @throws IndegoAuthenticationException if request was rejected as unauthorized
130      * @throws IndegoException if any communication or parsing error occurred
131      */
132     public RawType getMap() throws IndegoAuthenticationException, IndegoException {
133         return getRawRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/map");
134     }
135
136     /**
137      * Queries the calendar.
138      * 
139      * @return the calendar
140      * @throws IndegoAuthenticationException if request was rejected as unauthorized
141      * @throws IndegoException if any communication or parsing error occurred
142      */
143     public DeviceCalendarResponse getCalendar() throws IndegoAuthenticationException, IndegoException {
144         DeviceCalendarResponse calendar = getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/calendar",
145                 DeviceCalendarResponse.class);
146         return calendar;
147     }
148
149     /**
150      * Sends a command to the Indego device.
151      * 
152      * @param command the control command to send to the device
153      * @throws IndegoAuthenticationException if request was rejected as unauthorized
154      * @throws IndegoInvalidCommandException if the command was not processed correctly
155      * @throws IndegoException if any communication or parsing error occurred
156      */
157     public void sendCommand(DeviceCommand command)
158             throws IndegoAuthenticationException, IndegoInvalidCommandException, IndegoException {
159         SetStateRequest request = new SetStateRequest();
160         request.state = command.getActionCode();
161         putRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + serialNumber + "/state", request);
162     }
163
164     /**
165      * Queries the predictive weather forecast.
166      * 
167      * @return the weather forecast DTO
168      * @throws IndegoAuthenticationException if request was rejected as unauthorized
169      * @throws IndegoException if any communication or parsing error occurred
170      */
171     public LocationWeatherResponse getWeather() throws IndegoAuthenticationException, IndegoException {
172         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/weather", LocationWeatherResponse.class);
173     }
174
175     /**
176      * Queries the predictive adjustment.
177      * 
178      * @return the predictive adjustment
179      * @throws IndegoAuthenticationException if request was rejected as unauthorized
180      * @throws IndegoException if any communication or parsing error occurred
181      */
182     public int getPredictiveAdjustment() throws IndegoAuthenticationException, IndegoException {
183         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/useradjustment",
184                 PredictiveAdjustment.class).adjustment;
185     }
186
187     /**
188      * Sets the predictive adjustment.
189      * 
190      * @param adjust the predictive adjustment
191      * @throws IndegoAuthenticationException if request was rejected as unauthorized
192      * @throws IndegoException if any communication or parsing error occurred
193      */
194     public void setPredictiveAdjustment(final int adjust) throws IndegoAuthenticationException, IndegoException {
195         final PredictiveAdjustment adjustment = new PredictiveAdjustment();
196         adjustment.adjustment = adjust;
197         putRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/useradjustment", adjustment);
198     }
199
200     /**
201      * Queries predictive moving.
202      * 
203      * @return predictive moving
204      * @throws IndegoAuthenticationException if request was rejected as unauthorized
205      * @throws IndegoException if any communication or parsing error occurred
206      */
207     public boolean getPredictiveMoving() throws IndegoAuthenticationException, IndegoException {
208         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive", PredictiveStatus.class).enabled;
209     }
210
211     /**
212      * Sets predictive moving.
213      * 
214      * @param enable
215      * @throws IndegoAuthenticationException if request was rejected as unauthorized
216      * @throws IndegoException if any communication or parsing error occurred
217      */
218     public void setPredictiveMoving(final boolean enable) throws IndegoAuthenticationException, IndegoException {
219         final PredictiveStatus status = new PredictiveStatus();
220         status.enabled = enable;
221         putRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive", status);
222     }
223
224     /**
225      * Queries predictive last cutting as {@link Instant}.
226      * 
227      * @return predictive last cutting
228      * @throws IndegoAuthenticationException if request was rejected as unauthorized
229      * @throws IndegoException if any communication or parsing error occurred
230      */
231     public @Nullable Instant getPredictiveLastCutting() throws IndegoAuthenticationException, IndegoException {
232         try {
233             return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/lastcutting",
234                     PredictiveLastCuttingResponse.class).getLastCutting();
235         } catch (IndegoInvalidResponseException e) {
236             if (e.getHttpStatusCode() == HttpStatus.NO_CONTENT_204) {
237                 return null;
238             }
239             throw e;
240         }
241     }
242
243     /**
244      * Queries predictive next cutting as {@link Instant}.
245      * 
246      * @return predictive next cutting
247      * @throws IndegoAuthenticationException if request was rejected as unauthorized
248      * @throws IndegoException if any communication or parsing error occurred
249      */
250     public @Nullable Instant getPredictiveNextCutting() throws IndegoAuthenticationException, IndegoException {
251         try {
252             return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/nextcutting",
253                     PredictiveNextCuttingResponse.class).getNextCutting();
254         } catch (IndegoInvalidResponseException e) {
255             if (e.getHttpStatusCode() == HttpStatus.NO_CONTENT_204) {
256                 return null;
257             }
258             throw e;
259         }
260     }
261
262     /**
263      * Queries predictive exclusion time.
264      * 
265      * @return predictive exclusion time DTO
266      * @throws IndegoAuthenticationException if request was rejected as unauthorized
267      * @throws IndegoException if any communication or parsing error occurred
268      */
269     public DeviceCalendarResponse getPredictiveExclusionTime() throws IndegoAuthenticationException, IndegoException {
270         return getRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/calendar", DeviceCalendarResponse.class);
271     }
272
273     /**
274      * Sets predictive exclusion time.
275      * 
276      * @param calendar calendar DTO
277      * @throws IndegoAuthenticationException if request was rejected as unauthorized
278      * @throws IndegoException if any communication or parsing error occurred
279      */
280     public void setPredictiveExclusionTime(final DeviceCalendarResponse calendar)
281             throws IndegoAuthenticationException, IndegoException {
282         putRequestWithAuthentication(SERIAL_NUMBER_SUBPATH + serialNumber + "/predictive/calendar", calendar);
283     }
284
285     /**
286      * Request map position updates for the next ({@link count} * {@link interval}) number of seconds.
287      * 
288      * @param count number of updates
289      * @param interval number of seconds between updates
290      * @throws IndegoAuthenticationException if request was rejected as unauthorized
291      * @throws IndegoException if any communication or parsing error occurred
292      */
293     public void requestPosition(int count, int interval) throws IndegoAuthenticationException, IndegoException {
294         postRequest(SERIAL_NUMBER_SUBPATH + serialNumber + "/requestPosition?count=" + count + "&interval=" + interval);
295     }
296 }