2 * Copyright (c) 2010-2023 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.hdpowerview.internal;
15 import java.time.Duration;
16 import java.time.Instant;
17 import java.util.List;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeoutException;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.eclipse.jetty.client.api.ContentResponse;
25 import org.eclipse.jetty.client.api.Request;
26 import org.eclipse.jetty.client.util.StringContentProvider;
27 import org.eclipse.jetty.http.HttpHeader;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.openhab.binding.hdpowerview.internal.dto.Color;
31 import org.openhab.binding.hdpowerview.internal.dto.HubFirmware;
32 import org.openhab.binding.hdpowerview.internal.dto.Scene;
33 import org.openhab.binding.hdpowerview.internal.dto.SceneCollection;
34 import org.openhab.binding.hdpowerview.internal.dto.ScheduledEvent;
35 import org.openhab.binding.hdpowerview.internal.dto.ShadeData;
36 import org.openhab.binding.hdpowerview.internal.dto.ShadePosition;
37 import org.openhab.binding.hdpowerview.internal.dto.SurveyData;
38 import org.openhab.binding.hdpowerview.internal.dto.UserData;
39 import org.openhab.binding.hdpowerview.internal.dto.requests.RepeaterBlinking;
40 import org.openhab.binding.hdpowerview.internal.dto.requests.RepeaterColor;
41 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeCalibrate;
42 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeJog;
43 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeMove;
44 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeStop;
45 import org.openhab.binding.hdpowerview.internal.dto.responses.FirmwareVersion;
46 import org.openhab.binding.hdpowerview.internal.dto.responses.Repeater;
47 import org.openhab.binding.hdpowerview.internal.dto.responses.RepeaterData;
48 import org.openhab.binding.hdpowerview.internal.dto.responses.Repeaters;
49 import org.openhab.binding.hdpowerview.internal.dto.responses.SceneCollections;
50 import org.openhab.binding.hdpowerview.internal.dto.responses.Scenes;
51 import org.openhab.binding.hdpowerview.internal.dto.responses.ScheduledEvents;
52 import org.openhab.binding.hdpowerview.internal.dto.responses.Shade;
53 import org.openhab.binding.hdpowerview.internal.dto.responses.Shades;
54 import org.openhab.binding.hdpowerview.internal.dto.responses.Survey;
55 import org.openhab.binding.hdpowerview.internal.dto.responses.UserDataResponse;
56 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
57 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
58 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
59 import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
63 import com.google.gson.Gson;
64 import com.google.gson.JsonElement;
65 import com.google.gson.JsonObject;
66 import com.google.gson.JsonParseException;
67 import com.google.gson.JsonParser;
70 * JAX-RS targets for communicating with an HD PowerView hub
72 * @author Andy Lintner - Initial contribution
73 * @author Andrew Fiddian-Green - Added support for secondary rail positions
74 * @author Jacob Laursen - Added support for scene groups and automations
77 public class HDPowerViewWebTargets {
79 private final Logger logger = LoggerFactory.getLogger(HDPowerViewWebTargets.class);
82 * the hub returns a 423 error (resource locked) daily just after midnight;
83 * which means it is temporarily undergoing maintenance; so we use "soft"
84 * exception handling during the five minute maintenance period after a 423
87 private final Duration maintenancePeriod = Duration.ofMinutes(5);
88 private Instant maintenanceScheduledEnd = Instant.MIN;
90 private final String base;
91 private final String firmwareVersion;
92 private final String shades;
93 private final String sceneActivate;
94 private final String scenes;
95 private final String sceneCollectionActivate;
96 private final String sceneCollections;
97 private final String scheduledEvents;
98 private final String repeaters;
99 private final String userData;
101 private final Gson gson = new Gson();
102 private final HttpClient httpClient;
105 * helper class for passing http url query parameters
107 public static class Query {
108 private final String key;
109 private final String value;
111 private Query(String key, String value) {
116 public static Query of(String key, String value) {
117 return new Query(key, value);
120 public String getKey() {
124 public String getValue() {
129 public String toString() {
130 return String.format("?%s=%s", key, value);
135 * Initialize the web targets
137 * @param httpClient the HTTP client (the binding)
138 * @param ipAddress the IP address of the server (the hub)
140 public HDPowerViewWebTargets(HttpClient httpClient, String ipAddress) {
141 base = "http://" + ipAddress + "/api/";
142 shades = base + "shades/";
143 firmwareVersion = base + "fwversion";
144 sceneActivate = base + "scenes";
145 scenes = base + "scenes/";
147 // Hub v1 only supports "scenecollections". Hub v2 will redirect to "sceneCollections".
148 sceneCollectionActivate = base + "scenecollections";
149 sceneCollections = base + "scenecollections/";
151 scheduledEvents = base + "scheduledevents";
152 repeaters = base + "repeaters/";
153 userData = base + "userdata";
155 this.httpClient = httpClient;
159 * Fetches a JSON package with firmware information for the hub.
161 * @return FirmwareVersions class instance
162 * @throws HubInvalidResponseException if response is invalid
163 * @throws HubProcessingException if there is any processing error
164 * @throws HubMaintenanceException if the hub is down for maintenance
166 public HubFirmware getFirmwareVersions()
167 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
168 String json = invoke(HttpMethod.GET, firmwareVersion, null, null);
170 FirmwareVersion firmwareVersion = gson.fromJson(json, FirmwareVersion.class);
171 if (firmwareVersion == null) {
172 throw new HubInvalidResponseException("Missing firmware response");
174 HubFirmware firmwareVersions = firmwareVersion.firmware;
175 if (firmwareVersions == null) {
176 throw new HubInvalidResponseException("Missing 'firmware' element");
178 return firmwareVersions;
179 } catch (JsonParseException e) {
180 throw new HubInvalidResponseException("Error parsing firmware response", e);
185 * Fetches a JSON package with user data information for the hub.
187 * @return {@link UserData} class instance
188 * @throws HubInvalidResponseException if response is invalid
189 * @throws HubProcessingException if there is any processing error
190 * @throws HubMaintenanceException if the hub is down for maintenance
192 public UserData getUserData() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
193 String json = invoke(HttpMethod.GET, userData, null, null);
195 UserDataResponse userDataResponse = gson.fromJson(json, UserDataResponse.class);
196 if (userDataResponse == null) {
197 throw new HubInvalidResponseException("Missing userData response");
199 UserData userData = userDataResponse.userData;
200 if (userData == null) {
201 throw new HubInvalidResponseException("Missing 'userData' element");
204 } catch (JsonParseException e) {
205 throw new HubInvalidResponseException("Error parsing userData response", e);
210 * Fetches a JSON package that describes all shades in the hub, and wraps it in
211 * a Shades class instance
213 * @return Shades class instance
214 * @throws HubInvalidResponseException if response is invalid
215 * @throws HubProcessingException if there is any processing error
216 * @throws HubMaintenanceException if the hub is down for maintenance
218 public Shades getShades() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
219 String json = invoke(HttpMethod.GET, shades, null, null);
221 Shades shades = gson.fromJson(json, Shades.class);
222 if (shades == null) {
223 throw new HubInvalidResponseException("Missing shades response");
225 List<ShadeData> shadeData = shades.shadeData;
226 if (shadeData == null) {
227 throw new HubInvalidResponseException("Missing 'shades.shadeData' element");
230 } catch (JsonParseException e) {
231 throw new HubInvalidResponseException("Error parsing shades response", e);
236 * Instructs the hub to move a specific shade
238 * @param shadeId id of the shade to be moved
239 * @param position instance of ShadePosition containing the new position
240 * @return ShadeData class instance (with new position)
241 * @throws HubInvalidResponseException if response is invalid
242 * @throws HubProcessingException if there is any processing error
243 * @throws HubMaintenanceException if the hub is down for maintenance
244 * @throws HubShadeTimeoutException if the shade did not respond to a request
246 public ShadeData moveShade(int shadeId, ShadePosition position) throws HubInvalidResponseException,
247 HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
248 String jsonRequest = gson.toJson(new ShadeMove(position));
249 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
250 return shadeDataFromJson(jsonResponse);
253 private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException, HubShadeTimeoutException {
255 Shade shade = gson.fromJson(json, Shade.class);
257 throw new HubInvalidResponseException("Missing shade response");
259 ShadeData shadeData = shade.shade;
260 if (shadeData == null) {
261 throw new HubInvalidResponseException("Missing 'shade.shade' element");
263 if (Boolean.TRUE.equals(shadeData.timedOut)) {
264 throw new HubShadeTimeoutException("Timeout when sending request to the shade");
267 } catch (JsonParseException e) {
268 throw new HubInvalidResponseException("Error parsing shade response", e);
273 * Instructs the hub to stop movement of a specific shade
275 * @param shadeId id of the shade to be stopped
276 * @return ShadeData class instance (new position cannot be relied upon)
277 * @throws HubInvalidResponseException if response is invalid
278 * @throws HubProcessingException if there is any processing error
279 * @throws HubMaintenanceException if the hub is down for maintenance
280 * @throws HubShadeTimeoutException if the shade did not respond to a request
282 public ShadeData stopShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
283 HubMaintenanceException, HubShadeTimeoutException {
284 String jsonRequest = gson.toJson(new ShadeStop());
285 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
286 return shadeDataFromJson(jsonResponse);
290 * Instructs the hub to jog a specific shade
292 * @param shadeId id of the shade to be jogged
293 * @return ShadeData class instance
294 * @throws HubInvalidResponseException if response is invalid
295 * @throws HubProcessingException if there is any processing error
296 * @throws HubMaintenanceException if the hub is down for maintenance
297 * @throws HubShadeTimeoutException if the shade did not respond to a request
299 public ShadeData jogShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
300 HubMaintenanceException, HubShadeTimeoutException {
301 String jsonRequest = gson.toJson(new ShadeJog());
302 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
303 return shadeDataFromJson(jsonResponse);
307 * Instructs the hub to calibrate a specific shade
309 * @param shadeId id of the shade to be calibrated
310 * @return ShadeData class instance
311 * @throws HubInvalidResponseException if response is invalid
312 * @throws HubProcessingException if there is any processing error
313 * @throws HubMaintenanceException if the hub is down for maintenance
314 * @throws HubShadeTimeoutException if the shade did not respond to a request
316 public ShadeData calibrateShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
317 HubMaintenanceException, HubShadeTimeoutException {
318 String jsonRequest = gson.toJson(new ShadeCalibrate());
319 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
320 return shadeDataFromJson(jsonResponse);
324 * Fetches a JSON package that describes all scenes in the hub, and wraps it in
325 * a Scenes class instance
327 * @return Scenes class instance
328 * @throws HubInvalidResponseException if response is invalid
329 * @throws HubProcessingException if there is any processing error
330 * @throws HubMaintenanceException if the hub is down for maintenance
332 public Scenes getScenes() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
333 String json = invoke(HttpMethod.GET, scenes, null, null);
335 Scenes scenes = gson.fromJson(json, Scenes.class);
336 if (scenes == null) {
337 throw new HubInvalidResponseException("Missing scenes response");
339 List<Scene> sceneData = scenes.sceneData;
340 if (sceneData == null) {
341 throw new HubInvalidResponseException("Missing 'scenes.sceneData' element");
344 } catch (JsonParseException e) {
345 throw new HubInvalidResponseException("Error parsing scenes response", e);
350 * Instructs the hub to execute a specific scene
352 * @param sceneId id of the scene to be executed
353 * @throws HubProcessingException if there is any processing error
354 * @throws HubMaintenanceException if the hub is down for maintenance
356 public void activateScene(int sceneId) throws HubProcessingException, HubMaintenanceException {
357 invoke(HttpMethod.GET, sceneActivate, Query.of("sceneId", Integer.toString(sceneId)), null);
361 * Fetches a JSON package that describes all scene collections in the hub, and wraps it in
362 * a SceneCollections class instance
364 * @return SceneCollections class instance
365 * @throws HubInvalidResponseException if response is invalid
366 * @throws HubProcessingException if there is any processing error
367 * @throws HubMaintenanceException if the hub is down for maintenance
369 public SceneCollections getSceneCollections()
370 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
371 String json = invoke(HttpMethod.GET, sceneCollections, null, null);
373 SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
374 if (sceneCollections == null) {
375 throw new HubInvalidResponseException("Missing sceneCollections response");
377 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
378 if (sceneCollectionData == null) {
379 throw new HubInvalidResponseException("Missing 'sceneCollections.sceneCollectionData' element");
381 return sceneCollections;
382 } catch (JsonParseException e) {
383 throw new HubInvalidResponseException("Error parsing sceneCollections response", e);
388 * Instructs the hub to execute a specific scene collection
390 * @param sceneCollectionId id of the scene collection to be executed
391 * @throws HubProcessingException if there is any processing error
392 * @throws HubMaintenanceException if the hub is down for maintenance
394 public void activateSceneCollection(int sceneCollectionId) throws HubProcessingException, HubMaintenanceException {
395 invoke(HttpMethod.GET, sceneCollectionActivate,
396 Query.of("sceneCollectionId", Integer.toString(sceneCollectionId)), null);
400 * Fetches a JSON package that describes all scheduled events in the hub, and wraps it in
401 * a ScheduledEvents class instance
403 * @return ScheduledEvents class instance
404 * @throws HubInvalidResponseException if response is invalid
405 * @throws HubProcessingException if there is any processing error
406 * @throws HubMaintenanceException if the hub is down for maintenance
408 public ScheduledEvents getScheduledEvents()
409 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
410 String json = invoke(HttpMethod.GET, scheduledEvents, null, null);
412 ScheduledEvents scheduledEvents = gson.fromJson(json, ScheduledEvents.class);
413 if (scheduledEvents == null) {
414 throw new HubInvalidResponseException("Missing scheduledEvents response");
416 List<ScheduledEvent> scheduledEventData = scheduledEvents.scheduledEventData;
417 if (scheduledEventData == null) {
418 throw new HubInvalidResponseException("Missing 'scheduledEvents.scheduledEventData' element");
420 return scheduledEvents;
421 } catch (JsonParseException e) {
422 throw new HubInvalidResponseException("Error parsing scheduledEvents response", e);
427 * Enables or disables a scheduled event in the hub.
429 * @param scheduledEventId id of the scheduled event to be enabled or disabled
430 * @param enable true to enable scheduled event, false to disable
431 * @throws HubInvalidResponseException if response is invalid
432 * @throws HubProcessingException if there is any processing error
433 * @throws HubMaintenanceException if the hub is down for maintenance
435 public void enableScheduledEvent(int scheduledEventId, boolean enable)
436 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
437 String uri = scheduledEvents + "/" + scheduledEventId;
438 String jsonResponse = invoke(HttpMethod.GET, uri, null, null);
440 JsonObject jsonObject = JsonParser.parseString(jsonResponse).getAsJsonObject();
441 JsonElement scheduledEventElement = jsonObject.get("scheduledEvent");
442 if (scheduledEventElement == null) {
443 throw new HubInvalidResponseException("Missing 'scheduledEvent' element");
445 JsonObject scheduledEventObject = scheduledEventElement.getAsJsonObject();
446 scheduledEventObject.addProperty("enabled", enable);
447 invoke(HttpMethod.PUT, uri, null, jsonObject.toString());
448 } catch (JsonParseException | IllegalStateException e) {
449 throw new HubInvalidResponseException("Error parsing scheduledEvent response", e);
454 * Fetches a JSON package that describes all repeaters in the hub, and wraps it in
455 * a Repeaters class instance
457 * @return Repeaters class instance
458 * @throws HubInvalidResponseException if response is invalid
459 * @throws HubProcessingException if there is any processing error
460 * @throws HubMaintenanceException if the hub is down for maintenance
462 public Repeaters getRepeaters()
463 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
464 String json = invoke(HttpMethod.GET, repeaters, null, null);
466 Repeaters repeaters = gson.fromJson(json, Repeaters.class);
467 if (repeaters == null) {
468 throw new HubInvalidResponseException("Missing repeaters response");
470 List<RepeaterData> repeaterData = repeaters.repeaterData;
471 if (repeaterData == null) {
472 throw new HubInvalidResponseException("Missing 'repeaters.repeaterData' element");
475 } catch (JsonParseException e) {
476 throw new HubInvalidResponseException("Error parsing repeaters response", e);
481 * Fetches a JSON package that describes a specific repeater in the hub, and wraps it
482 * in a RepeaterData class instance
484 * @param repeaterId id of the repeater to be fetched
485 * @return RepeaterData class instance
486 * @throws HubInvalidResponseException if response is invalid
487 * @throws HubProcessingException if there is any processing error
488 * @throws HubMaintenanceException if the hub is down for maintenance
490 public RepeaterData getRepeater(int repeaterId)
491 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
492 String jsonResponse = invoke(HttpMethod.GET, repeaters + Integer.toString(repeaterId), null, null);
493 return repeaterDataFromJson(jsonResponse);
496 private RepeaterData repeaterDataFromJson(String json) throws HubInvalidResponseException {
498 Repeater repeater = gson.fromJson(json, Repeater.class);
499 if (repeater == null) {
500 throw new HubInvalidResponseException("Missing repeater response");
502 RepeaterData repeaterData = repeater.repeater;
503 if (repeaterData == null) {
504 throw new HubInvalidResponseException("Missing 'repeater.repeater' element");
507 } catch (JsonParseException e) {
508 throw new HubInvalidResponseException("Error parsing repeater response", e);
513 * Instructs the hub to identify a specific repeater by blinking
515 * @param repeaterId id of the repeater to be identified
516 * @return RepeaterData class instance
517 * @throws HubInvalidResponseException if response is invalid
518 * @throws HubProcessingException if there is any processing error
519 * @throws HubMaintenanceException if the hub is down for maintenance
521 public RepeaterData identifyRepeater(int repeaterId)
522 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
523 String jsonResponse = invoke(HttpMethod.GET, repeaters + repeaterId,
524 Query.of("identify", Boolean.toString(true)), null);
525 return repeaterDataFromJson(jsonResponse);
529 * Enables or disables blinking for a repeater
531 * @param repeaterId id of the repeater for which to be enable or disable blinking
532 * @param enable true to enable blinking, false to disable
533 * @return RepeaterData class instance
534 * @throws HubInvalidResponseException if response is invalid
535 * @throws HubProcessingException if there is any processing error
536 * @throws HubMaintenanceException if the hub is down for maintenance
538 public RepeaterData enableRepeaterBlinking(int repeaterId, boolean enable)
539 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
540 String jsonRequest = gson.toJson(new RepeaterBlinking(repeaterId, enable));
541 String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest);
542 return repeaterDataFromJson(jsonResponse);
546 * Sets color and brightness for a repeater
548 * @param repeaterId id of the repeater for which to set color and brightness
549 * @return RepeaterData class instance
550 * @throws HubInvalidResponseException if response is invalid
551 * @throws HubProcessingException if there is any processing error
552 * @throws HubMaintenanceException if the hub is down for maintenance
554 public RepeaterData setRepeaterColor(int repeaterId, Color color)
555 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
556 String jsonRequest = gson.toJson(new RepeaterColor(repeaterId, color));
557 String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest);
558 return repeaterDataFromJson(jsonResponse);
562 * Invoke a call on the hub server to retrieve information or send a command
564 * @param method GET or PUT
565 * @param url the host url to be called
566 * @param query the http query parameter
567 * @param jsonCommand the request command content (as a json string)
568 * @return the response content (as a json string)
569 * @throws HubMaintenanceException
570 * @throws HubProcessingException
572 private synchronized String invoke(HttpMethod method, String url, @Nullable Query query,
573 @Nullable String jsonCommand) throws HubMaintenanceException, HubProcessingException {
574 if (logger.isTraceEnabled()) {
576 logger.trace("API command {} {}{}", method, url, query);
578 logger.trace("API command {} {}", method, url);
580 if (jsonCommand != null) {
581 logger.trace("JSON command = {}", jsonCommand);
584 Request request = httpClient.newRequest(url).method(method).header("Connection", "close").accept("*/*");
586 request.param(query.getKey(), query.getValue());
588 if (jsonCommand != null) {
589 request.header(HttpHeader.CONTENT_TYPE, "application/json").content(new StringContentProvider(jsonCommand));
591 ContentResponse response;
593 response = request.send();
594 } catch (InterruptedException e) {
595 Thread.currentThread().interrupt();
596 throw new HubProcessingException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
597 } catch (TimeoutException | ExecutionException e) {
598 if (Instant.now().isBefore(maintenanceScheduledEnd)) {
599 // throw "softer" exception during maintenance window
600 logger.debug("Hub still undergoing maintenance");
601 throw new HubMaintenanceException("Hub still undergoing maintenance");
603 throw new HubProcessingException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
605 int statusCode = response.getStatus();
606 if (statusCode == HttpStatus.LOCKED_423) {
607 // set end of maintenance window, and throw a "softer" exception
608 maintenanceScheduledEnd = Instant.now().plus(maintenancePeriod);
609 logger.debug("Hub undergoing maintenance");
610 throw new HubMaintenanceException("Hub undergoing maintenance");
612 if (statusCode != HttpStatus.OK_200) {
613 logger.warn("Hub returned HTTP {} {}", statusCode, response.getReason());
614 throw new HubProcessingException(String.format("HTTP %d error", statusCode));
616 String jsonResponse = response.getContentAsString();
617 if (logger.isTraceEnabled()) {
618 logger.trace("JSON response = {}", jsonResponse);
620 if (jsonResponse == null || jsonResponse.isEmpty()) {
621 logger.warn("Hub returned no content");
622 throw new HubProcessingException("Missing response entity");
628 * Fetches a JSON package that describes a specific shade in the hub, and wraps it
629 * in a Shade class instance
631 * @param shadeId id of the shade to be fetched
632 * @return ShadeData class instance
633 * @throws HubInvalidResponseException if response is invalid
634 * @throws HubProcessingException if there is any processing error
635 * @throws HubMaintenanceException if the hub is down for maintenance
636 * @throws HubShadeTimeoutException if the shade did not respond to a request
638 public ShadeData getShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
639 HubMaintenanceException, HubShadeTimeoutException {
640 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null);
641 return shadeDataFromJson(jsonResponse);
645 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
646 * a specific shade's position; fetches a JSON package that describes that shade,
647 * and wraps it in a Shade class instance
649 * @param shadeId id of the shade to be refreshed
650 * @return ShadeData class instance
651 * @throws HubInvalidResponseException if response is invalid
652 * @throws HubProcessingException if there is any processing error
653 * @throws HubMaintenanceException if the hub is down for maintenance
654 * @throws HubShadeTimeoutException if the shade did not respond to a request
656 public ShadeData refreshShadePosition(int shadeId)
657 throws JsonParseException, HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
658 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
659 Query.of("refresh", Boolean.toString(true)), null);
660 return shadeDataFromJson(jsonResponse);
664 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
665 * a specific shade's survey data, which will also refresh signal strength;
666 * fetches a JSON package that describes that survey, and wraps it in a Survey
669 * @param shadeId id of the shade to be surveyed
670 * @return List of SurveyData class instances
671 * @throws HubInvalidResponseException if response is invalid
672 * @throws HubProcessingException if there is any processing error
673 * @throws HubMaintenanceException if the hub is down for maintenance
675 public List<SurveyData> getShadeSurvey(int shadeId)
676 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
677 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
678 Query.of("survey", Boolean.toString(true)), null);
680 Survey survey = gson.fromJson(jsonResponse, Survey.class);
681 if (survey == null) {
682 throw new HubInvalidResponseException("Missing survey response");
684 List<SurveyData> surveyData = survey.surveyData;
685 if (surveyData == null) {
686 throw new HubInvalidResponseException("Missing 'survey.surveyData' element");
689 } catch (JsonParseException e) {
690 throw new HubInvalidResponseException("Error parsing survey response", e);
695 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
696 * a specific shade's battery level; fetches a JSON package that describes that shade,
697 * and wraps it in a Shade class instance
699 * @param shadeId id of the shade to be refreshed
700 * @return ShadeData class instance
701 * @throws HubInvalidResponseException if response is invalid
702 * @throws HubProcessingException if there is any processing error
703 * @throws HubMaintenanceException if the hub is down for maintenance
704 * @throws HubShadeTimeoutException if the shade did not respond to a request
706 public ShadeData refreshShadeBatteryLevel(int shadeId) throws HubInvalidResponseException, HubProcessingException,
707 HubMaintenanceException, HubShadeTimeoutException {
708 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
709 Query.of("updateBatteryLevel", Boolean.toString(true)), null);
710 return shadeDataFromJson(jsonResponse);