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.TimeUnit;
20 import java.util.concurrent.TimeoutException;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentResponse;
26 import org.eclipse.jetty.client.api.Request;
27 import org.eclipse.jetty.client.util.StringContentProvider;
28 import org.eclipse.jetty.http.HttpHeader;
29 import org.eclipse.jetty.http.HttpMethod;
30 import org.eclipse.jetty.http.HttpStatus;
31 import org.openhab.binding.hdpowerview.internal.dto.Color;
32 import org.openhab.binding.hdpowerview.internal.dto.HubFirmware;
33 import org.openhab.binding.hdpowerview.internal.dto.Scene;
34 import org.openhab.binding.hdpowerview.internal.dto.SceneCollection;
35 import org.openhab.binding.hdpowerview.internal.dto.ScheduledEvent;
36 import org.openhab.binding.hdpowerview.internal.dto.ShadeData;
37 import org.openhab.binding.hdpowerview.internal.dto.ShadePosition;
38 import org.openhab.binding.hdpowerview.internal.dto.SurveyData;
39 import org.openhab.binding.hdpowerview.internal.dto.UserData;
40 import org.openhab.binding.hdpowerview.internal.dto.requests.RepeaterBlinking;
41 import org.openhab.binding.hdpowerview.internal.dto.requests.RepeaterColor;
42 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeCalibrate;
43 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeJog;
44 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeMove;
45 import org.openhab.binding.hdpowerview.internal.dto.requests.ShadeStop;
46 import org.openhab.binding.hdpowerview.internal.dto.responses.FirmwareVersion;
47 import org.openhab.binding.hdpowerview.internal.dto.responses.Repeater;
48 import org.openhab.binding.hdpowerview.internal.dto.responses.RepeaterData;
49 import org.openhab.binding.hdpowerview.internal.dto.responses.Repeaters;
50 import org.openhab.binding.hdpowerview.internal.dto.responses.SceneCollections;
51 import org.openhab.binding.hdpowerview.internal.dto.responses.Scenes;
52 import org.openhab.binding.hdpowerview.internal.dto.responses.ScheduledEvents;
53 import org.openhab.binding.hdpowerview.internal.dto.responses.Shade;
54 import org.openhab.binding.hdpowerview.internal.dto.responses.Shades;
55 import org.openhab.binding.hdpowerview.internal.dto.responses.Survey;
56 import org.openhab.binding.hdpowerview.internal.dto.responses.UserDataResponse;
57 import org.openhab.binding.hdpowerview.internal.exceptions.HubInvalidResponseException;
58 import org.openhab.binding.hdpowerview.internal.exceptions.HubMaintenanceException;
59 import org.openhab.binding.hdpowerview.internal.exceptions.HubProcessingException;
60 import org.openhab.binding.hdpowerview.internal.exceptions.HubShadeTimeoutException;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import com.google.gson.Gson;
65 import com.google.gson.JsonElement;
66 import com.google.gson.JsonObject;
67 import com.google.gson.JsonParseException;
68 import com.google.gson.JsonParser;
71 * JAX-RS targets for communicating with an HD PowerView hub
73 * @author Andy Lintner - Initial contribution
74 * @author Andrew Fiddian-Green - Added support for secondary rail positions
75 * @author Jacob Laursen - Added support for scene groups and automations
78 public class HDPowerViewWebTargets {
79 private static final int REQUEST_TIMEOUT_MS = 30_000;
81 private final Logger logger = LoggerFactory.getLogger(HDPowerViewWebTargets.class);
84 * the hub returns a 423 error (resource locked) daily just after midnight;
85 * which means it is temporarily undergoing maintenance; so we use "soft"
86 * exception handling during the five minute maintenance period after a 423
89 private final Duration maintenancePeriod = Duration.ofMinutes(5);
90 private Instant maintenanceScheduledEnd = Instant.MIN;
92 private final String base;
93 private final String firmwareVersion;
94 private final String shades;
95 private final String sceneActivate;
96 private final String scenes;
97 private final String sceneCollectionActivate;
98 private final String sceneCollections;
99 private final String scheduledEvents;
100 private final String repeaters;
101 private final String userData;
103 private final Gson gson = new Gson();
104 private final HttpClient httpClient;
107 * helper class for passing http url query parameters
109 public static class Query {
110 private final String key;
111 private final String value;
113 private Query(String key, String value) {
118 public static Query of(String key, String value) {
119 return new Query(key, value);
122 public String getKey() {
126 public String getValue() {
131 public String toString() {
132 return String.format("?%s=%s", key, value);
137 * Initialize the web targets
139 * @param httpClient the HTTP client (the binding)
140 * @param ipAddress the IP address of the server (the hub)
142 public HDPowerViewWebTargets(HttpClient httpClient, String ipAddress) {
143 base = "http://" + ipAddress + "/api/";
144 shades = base + "shades/";
145 firmwareVersion = base + "fwversion";
146 sceneActivate = base + "scenes";
147 scenes = base + "scenes/";
149 // Hub v1 only supports "scenecollections". Hub v2 will redirect to "sceneCollections".
150 sceneCollectionActivate = base + "scenecollections";
151 sceneCollections = base + "scenecollections/";
153 scheduledEvents = base + "scheduledevents";
154 repeaters = base + "repeaters/";
155 userData = base + "userdata";
157 this.httpClient = httpClient;
161 * Fetches a JSON package with firmware information for the hub.
163 * @return FirmwareVersions class instance
164 * @throws HubInvalidResponseException if response is invalid
165 * @throws HubProcessingException if there is any processing error
166 * @throws HubMaintenanceException if the hub is down for maintenance
168 public HubFirmware getFirmwareVersions()
169 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
170 String json = invoke(HttpMethod.GET, firmwareVersion, null, null);
172 FirmwareVersion firmwareVersion = gson.fromJson(json, FirmwareVersion.class);
173 if (firmwareVersion == null) {
174 throw new HubInvalidResponseException("Missing firmware response");
176 HubFirmware firmwareVersions = firmwareVersion.firmware;
177 if (firmwareVersions == null) {
178 throw new HubInvalidResponseException("Missing 'firmware' element");
180 return firmwareVersions;
181 } catch (JsonParseException e) {
182 throw new HubInvalidResponseException("Error parsing firmware response", e);
187 * Fetches a JSON package with user data information for the hub.
189 * @return {@link UserData} class instance
190 * @throws HubInvalidResponseException if response is invalid
191 * @throws HubProcessingException if there is any processing error
192 * @throws HubMaintenanceException if the hub is down for maintenance
194 public UserData getUserData() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
195 String json = invoke(HttpMethod.GET, userData, null, null);
197 UserDataResponse userDataResponse = gson.fromJson(json, UserDataResponse.class);
198 if (userDataResponse == null) {
199 throw new HubInvalidResponseException("Missing userData response");
201 UserData userData = userDataResponse.userData;
202 if (userData == null) {
203 throw new HubInvalidResponseException("Missing 'userData' element");
206 } catch (JsonParseException e) {
207 throw new HubInvalidResponseException("Error parsing userData response", e);
212 * Fetches a JSON package that describes all shades in the hub, and wraps it in
213 * a Shades class instance
215 * @return Shades class instance
216 * @throws HubInvalidResponseException if response is invalid
217 * @throws HubProcessingException if there is any processing error
218 * @throws HubMaintenanceException if the hub is down for maintenance
220 public Shades getShades() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
221 String json = invoke(HttpMethod.GET, shades, null, null);
223 Shades shades = gson.fromJson(json, Shades.class);
224 if (shades == null) {
225 throw new HubInvalidResponseException("Missing shades response");
227 List<ShadeData> shadeData = shades.shadeData;
228 if (shadeData == null) {
229 throw new HubInvalidResponseException("Missing 'shades.shadeData' element");
232 } catch (JsonParseException e) {
233 throw new HubInvalidResponseException("Error parsing shades response", e);
238 * Instructs the hub to move a specific shade
240 * @param shadeId id of the shade to be moved
241 * @param position instance of ShadePosition containing the new position
242 * @return ShadeData class instance (with new position)
243 * @throws HubInvalidResponseException if response is invalid
244 * @throws HubProcessingException if there is any processing error
245 * @throws HubMaintenanceException if the hub is down for maintenance
246 * @throws HubShadeTimeoutException if the shade did not respond to a request
248 public ShadeData moveShade(int shadeId, ShadePosition position) throws HubInvalidResponseException,
249 HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
250 String jsonRequest = gson.toJson(new ShadeMove(position));
251 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
252 return shadeDataFromJson(jsonResponse);
255 private ShadeData shadeDataFromJson(String json) throws HubInvalidResponseException, HubShadeTimeoutException {
257 Shade shade = gson.fromJson(json, Shade.class);
259 throw new HubInvalidResponseException("Missing shade response");
261 ShadeData shadeData = shade.shade;
262 if (shadeData == null) {
263 throw new HubInvalidResponseException("Missing 'shade.shade' element");
265 if (Boolean.TRUE.equals(shadeData.timedOut)) {
266 throw new HubShadeTimeoutException("Timeout when sending request to the shade");
269 } catch (JsonParseException e) {
270 throw new HubInvalidResponseException("Error parsing shade response", e);
275 * Instructs the hub to stop movement of a specific shade
277 * @param shadeId id of the shade to be stopped
278 * @return ShadeData class instance (new position cannot be relied upon)
279 * @throws HubInvalidResponseException if response is invalid
280 * @throws HubProcessingException if there is any processing error
281 * @throws HubMaintenanceException if the hub is down for maintenance
282 * @throws HubShadeTimeoutException if the shade did not respond to a request
284 public ShadeData stopShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
285 HubMaintenanceException, HubShadeTimeoutException {
286 String jsonRequest = gson.toJson(new ShadeStop());
287 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
288 return shadeDataFromJson(jsonResponse);
292 * Instructs the hub to jog a specific shade
294 * @param shadeId id of the shade to be jogged
295 * @return ShadeData class instance
296 * @throws HubInvalidResponseException if response is invalid
297 * @throws HubProcessingException if there is any processing error
298 * @throws HubMaintenanceException if the hub is down for maintenance
299 * @throws HubShadeTimeoutException if the shade did not respond to a request
301 public ShadeData jogShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
302 HubMaintenanceException, HubShadeTimeoutException {
303 String jsonRequest = gson.toJson(new ShadeJog());
304 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
305 return shadeDataFromJson(jsonResponse);
309 * Instructs the hub to calibrate a specific shade
311 * @param shadeId id of the shade to be calibrated
312 * @return ShadeData class instance
313 * @throws HubInvalidResponseException if response is invalid
314 * @throws HubProcessingException if there is any processing error
315 * @throws HubMaintenanceException if the hub is down for maintenance
316 * @throws HubShadeTimeoutException if the shade did not respond to a request
318 public ShadeData calibrateShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
319 HubMaintenanceException, HubShadeTimeoutException {
320 String jsonRequest = gson.toJson(new ShadeCalibrate());
321 String jsonResponse = invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, jsonRequest);
322 return shadeDataFromJson(jsonResponse);
326 * Fetches a JSON package that describes all scenes in the hub, and wraps it in
327 * a Scenes class instance
329 * @return Scenes class instance
330 * @throws HubInvalidResponseException if response is invalid
331 * @throws HubProcessingException if there is any processing error
332 * @throws HubMaintenanceException if the hub is down for maintenance
334 public Scenes getScenes() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
335 String json = invoke(HttpMethod.GET, scenes, null, null);
337 Scenes scenes = gson.fromJson(json, Scenes.class);
338 if (scenes == null) {
339 throw new HubInvalidResponseException("Missing scenes response");
341 List<Scene> sceneData = scenes.sceneData;
342 if (sceneData == null) {
343 throw new HubInvalidResponseException("Missing 'scenes.sceneData' element");
346 } catch (JsonParseException e) {
347 throw new HubInvalidResponseException("Error parsing scenes response", e);
352 * Instructs the hub to execute a specific scene
354 * @param sceneId id of the scene to be executed
355 * @throws HubProcessingException if there is any processing error
356 * @throws HubMaintenanceException if the hub is down for maintenance
358 public void activateScene(int sceneId) throws HubProcessingException, HubMaintenanceException {
359 invoke(HttpMethod.GET, sceneActivate, Query.of("sceneId", Integer.toString(sceneId)), null);
363 * Fetches a JSON package that describes all scene collections in the hub, and wraps it in
364 * a SceneCollections class instance
366 * @return SceneCollections class instance
367 * @throws HubInvalidResponseException if response is invalid
368 * @throws HubProcessingException if there is any processing error
369 * @throws HubMaintenanceException if the hub is down for maintenance
371 public SceneCollections getSceneCollections()
372 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
373 String json = invoke(HttpMethod.GET, sceneCollections, null, null);
375 SceneCollections sceneCollections = gson.fromJson(json, SceneCollections.class);
376 if (sceneCollections == null) {
377 throw new HubInvalidResponseException("Missing sceneCollections response");
379 List<SceneCollection> sceneCollectionData = sceneCollections.sceneCollectionData;
380 if (sceneCollectionData == null) {
381 throw new HubInvalidResponseException("Missing 'sceneCollections.sceneCollectionData' element");
383 return sceneCollections;
384 } catch (JsonParseException e) {
385 throw new HubInvalidResponseException("Error parsing sceneCollections response", e);
390 * Instructs the hub to execute a specific scene collection
392 * @param sceneCollectionId id of the scene collection to be executed
393 * @throws HubProcessingException if there is any processing error
394 * @throws HubMaintenanceException if the hub is down for maintenance
396 public void activateSceneCollection(int sceneCollectionId) throws HubProcessingException, HubMaintenanceException {
397 invoke(HttpMethod.GET, sceneCollectionActivate,
398 Query.of("sceneCollectionId", Integer.toString(sceneCollectionId)), null);
402 * Fetches a JSON package that describes all scheduled events in the hub, and wraps it in
403 * a ScheduledEvents class instance
405 * @return ScheduledEvents class instance
406 * @throws HubInvalidResponseException if response is invalid
407 * @throws HubProcessingException if there is any processing error
408 * @throws HubMaintenanceException if the hub is down for maintenance
410 public ScheduledEvents getScheduledEvents()
411 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
412 String json = invoke(HttpMethod.GET, scheduledEvents, null, null);
414 ScheduledEvents scheduledEvents = gson.fromJson(json, ScheduledEvents.class);
415 if (scheduledEvents == null) {
416 throw new HubInvalidResponseException("Missing scheduledEvents response");
418 List<ScheduledEvent> scheduledEventData = scheduledEvents.scheduledEventData;
419 if (scheduledEventData == null) {
420 throw new HubInvalidResponseException("Missing 'scheduledEvents.scheduledEventData' element");
422 return scheduledEvents;
423 } catch (JsonParseException e) {
424 throw new HubInvalidResponseException("Error parsing scheduledEvents response", e);
429 * Enables or disables a scheduled event in the hub.
431 * @param scheduledEventId id of the scheduled event to be enabled or disabled
432 * @param enable true to enable scheduled event, false to disable
433 * @throws HubInvalidResponseException if response is invalid
434 * @throws HubProcessingException if there is any processing error
435 * @throws HubMaintenanceException if the hub is down for maintenance
437 public void enableScheduledEvent(int scheduledEventId, boolean enable)
438 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
439 String uri = scheduledEvents + "/" + scheduledEventId;
440 String jsonResponse = invoke(HttpMethod.GET, uri, null, null);
442 JsonObject jsonObject = JsonParser.parseString(jsonResponse).getAsJsonObject();
443 JsonElement scheduledEventElement = jsonObject.get("scheduledEvent");
444 if (scheduledEventElement == null) {
445 throw new HubInvalidResponseException("Missing 'scheduledEvent' element");
447 JsonObject scheduledEventObject = scheduledEventElement.getAsJsonObject();
448 scheduledEventObject.addProperty("enabled", enable);
449 invoke(HttpMethod.PUT, uri, null, jsonObject.toString());
450 } catch (JsonParseException | IllegalStateException e) {
451 throw new HubInvalidResponseException("Error parsing scheduledEvent response", e);
456 * Fetches a JSON package that describes all repeaters in the hub, and wraps it in
457 * a Repeaters class instance
459 * @return Repeaters class instance
460 * @throws HubInvalidResponseException if response is invalid
461 * @throws HubProcessingException if there is any processing error
462 * @throws HubMaintenanceException if the hub is down for maintenance
464 public Repeaters getRepeaters()
465 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
466 String json = invoke(HttpMethod.GET, repeaters, null, null);
468 Repeaters repeaters = gson.fromJson(json, Repeaters.class);
469 if (repeaters == null) {
470 throw new HubInvalidResponseException("Missing repeaters response");
472 List<RepeaterData> repeaterData = repeaters.repeaterData;
473 if (repeaterData == null) {
474 throw new HubInvalidResponseException("Missing 'repeaters.repeaterData' element");
477 } catch (JsonParseException e) {
478 throw new HubInvalidResponseException("Error parsing repeaters response", e);
483 * Fetches a JSON package that describes a specific repeater in the hub, and wraps it
484 * in a RepeaterData class instance
486 * @param repeaterId id of the repeater to be fetched
487 * @return RepeaterData class instance
488 * @throws HubInvalidResponseException if response is invalid
489 * @throws HubProcessingException if there is any processing error
490 * @throws HubMaintenanceException if the hub is down for maintenance
492 public RepeaterData getRepeater(int repeaterId)
493 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
494 String jsonResponse = invoke(HttpMethod.GET, repeaters + Integer.toString(repeaterId), null, null);
495 return repeaterDataFromJson(jsonResponse);
498 private RepeaterData repeaterDataFromJson(String json) throws HubInvalidResponseException {
500 Repeater repeater = gson.fromJson(json, Repeater.class);
501 if (repeater == null) {
502 throw new HubInvalidResponseException("Missing repeater response");
504 RepeaterData repeaterData = repeater.repeater;
505 if (repeaterData == null) {
506 throw new HubInvalidResponseException("Missing 'repeater.repeater' element");
509 } catch (JsonParseException e) {
510 throw new HubInvalidResponseException("Error parsing repeater response", e);
515 * Instructs the hub to identify a specific repeater by blinking
517 * @param repeaterId id of the repeater to be identified
518 * @return RepeaterData class instance
519 * @throws HubInvalidResponseException if response is invalid
520 * @throws HubProcessingException if there is any processing error
521 * @throws HubMaintenanceException if the hub is down for maintenance
523 public RepeaterData identifyRepeater(int repeaterId)
524 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
525 String jsonResponse = invoke(HttpMethod.GET, repeaters + repeaterId,
526 Query.of("identify", Boolean.toString(true)), null);
527 return repeaterDataFromJson(jsonResponse);
531 * Enables or disables blinking for a repeater
533 * @param repeaterId id of the repeater for which to be enable or disable blinking
534 * @param enable true to enable blinking, false to disable
535 * @return RepeaterData class instance
536 * @throws HubInvalidResponseException if response is invalid
537 * @throws HubProcessingException if there is any processing error
538 * @throws HubMaintenanceException if the hub is down for maintenance
540 public RepeaterData enableRepeaterBlinking(int repeaterId, boolean enable)
541 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
542 String jsonRequest = gson.toJson(new RepeaterBlinking(repeaterId, enable));
543 String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest);
544 return repeaterDataFromJson(jsonResponse);
548 * Sets color and brightness for a repeater
550 * @param repeaterId id of the repeater for which to set color and brightness
551 * @return RepeaterData class instance
552 * @throws HubInvalidResponseException if response is invalid
553 * @throws HubProcessingException if there is any processing error
554 * @throws HubMaintenanceException if the hub is down for maintenance
556 public RepeaterData setRepeaterColor(int repeaterId, Color color)
557 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
558 String jsonRequest = gson.toJson(new RepeaterColor(repeaterId, color));
559 String jsonResponse = invoke(HttpMethod.PUT, repeaters + repeaterId, null, jsonRequest);
560 return repeaterDataFromJson(jsonResponse);
564 * Invoke a call on the hub server to retrieve information or send a command
566 * @param method GET or PUT
567 * @param url the host url to be called
568 * @param query the http query parameter
569 * @param jsonCommand the request command content (as a json string)
570 * @return the response content (as a json string)
571 * @throws HubMaintenanceException
572 * @throws HubProcessingException
574 private synchronized String invoke(HttpMethod method, String url, @Nullable Query query,
575 @Nullable String jsonCommand) throws HubMaintenanceException, HubProcessingException {
576 if (logger.isTraceEnabled()) {
578 logger.trace("API command {} {}{}", method, url, query);
580 logger.trace("API command {} {}", method, url);
582 if (jsonCommand != null) {
583 logger.trace("JSON command = {}", jsonCommand);
586 Request request = httpClient.newRequest(url).method(method).header("Connection", "close").accept("*/*")
587 .timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
589 request.param(query.getKey(), query.getValue());
591 if (jsonCommand != null) {
592 request.header(HttpHeader.CONTENT_TYPE, "application/json").content(new StringContentProvider(jsonCommand));
594 ContentResponse response;
596 response = request.send();
597 } catch (InterruptedException e) {
598 Thread.currentThread().interrupt();
599 throw new HubProcessingException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
600 } catch (TimeoutException | ExecutionException e) {
601 if (Instant.now().isBefore(maintenanceScheduledEnd)) {
602 // throw "softer" exception during maintenance window
603 logger.debug("Hub still undergoing maintenance");
604 throw new HubMaintenanceException("Hub still undergoing maintenance");
606 throw new HubProcessingException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage()));
608 int statusCode = response.getStatus();
609 if (statusCode == HttpStatus.LOCKED_423) {
610 // set end of maintenance window, and throw a "softer" exception
611 maintenanceScheduledEnd = Instant.now().plus(maintenancePeriod);
612 logger.debug("Hub undergoing maintenance");
613 throw new HubMaintenanceException("Hub undergoing maintenance");
615 if (statusCode != HttpStatus.OK_200) {
616 logger.warn("Hub returned HTTP {} {}", statusCode, response.getReason());
617 throw new HubProcessingException(String.format("HTTP %d error", statusCode));
619 String jsonResponse = response.getContentAsString();
620 if (logger.isTraceEnabled()) {
621 logger.trace("JSON response = {}", jsonResponse);
623 if (jsonResponse == null || jsonResponse.isEmpty()) {
624 logger.warn("Hub returned no content");
625 throw new HubProcessingException("Missing response entity");
631 * Fetches a JSON package that describes a specific shade in the hub, and wraps it
632 * in a Shade class instance
634 * @param shadeId id of the shade to be fetched
635 * @return ShadeData class instance
636 * @throws HubInvalidResponseException if response is invalid
637 * @throws HubProcessingException if there is any processing error
638 * @throws HubMaintenanceException if the hub is down for maintenance
639 * @throws HubShadeTimeoutException if the shade did not respond to a request
641 public ShadeData getShade(int shadeId) throws HubInvalidResponseException, HubProcessingException,
642 HubMaintenanceException, HubShadeTimeoutException {
643 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null);
644 return shadeDataFromJson(jsonResponse);
648 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
649 * a specific shade's position; fetches a JSON package that describes that shade,
650 * and wraps it in a Shade class instance
652 * @param shadeId id of the shade to be refreshed
653 * @return ShadeData class instance
654 * @throws HubInvalidResponseException if response is invalid
655 * @throws HubProcessingException if there is any processing error
656 * @throws HubMaintenanceException if the hub is down for maintenance
657 * @throws HubShadeTimeoutException if the shade did not respond to a request
659 public ShadeData refreshShadePosition(int shadeId)
660 throws JsonParseException, HubProcessingException, HubMaintenanceException, HubShadeTimeoutException {
661 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
662 Query.of("refresh", Boolean.toString(true)), null);
663 return shadeDataFromJson(jsonResponse);
667 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
668 * a specific shade's survey data, which will also refresh signal strength;
669 * fetches a JSON package that describes that survey, and wraps it in a Survey
672 * @param shadeId id of the shade to be surveyed
673 * @return List of SurveyData class instances
674 * @throws HubInvalidResponseException if response is invalid
675 * @throws HubProcessingException if there is any processing error
676 * @throws HubMaintenanceException if the hub is down for maintenance
678 public List<SurveyData> getShadeSurvey(int shadeId)
679 throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
680 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
681 Query.of("survey", Boolean.toString(true)), null);
683 Survey survey = gson.fromJson(jsonResponse, Survey.class);
684 if (survey == null) {
685 throw new HubInvalidResponseException("Missing survey response");
687 List<SurveyData> surveyData = survey.surveyData;
688 if (surveyData == null) {
689 throw new HubInvalidResponseException("Missing 'survey.surveyData' element");
692 } catch (JsonParseException e) {
693 throw new HubInvalidResponseException("Error parsing survey response", e);
698 * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on
699 * a specific shade's battery level; fetches a JSON package that describes that shade,
700 * and wraps it in a Shade class instance
702 * @param shadeId id of the shade to be refreshed
703 * @return ShadeData class instance
704 * @throws HubInvalidResponseException if response is invalid
705 * @throws HubProcessingException if there is any processing error
706 * @throws HubMaintenanceException if the hub is down for maintenance
707 * @throws HubShadeTimeoutException if the shade did not respond to a request
709 public ShadeData refreshShadeBatteryLevel(int shadeId) throws HubInvalidResponseException, HubProcessingException,
710 HubMaintenanceException, HubShadeTimeoutException {
711 String jsonResponse = invoke(HttpMethod.GET, shades + Integer.toString(shadeId),
712 Query.of("updateBatteryLevel", Boolean.toString(true)), null);
713 return shadeDataFromJson(jsonResponse);