]> git.basschouten.com Git - openhab-addons.git/blob
177ee4769b246761b35e109204a26424f66224d8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.innogysmarthome.internal.client;
14
15 import static org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants.*;
16 import static org.openhab.binding.innogysmarthome.internal.client.Constants.*;
17
18 import java.io.IOException;
19 import java.net.URI;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.HashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.TimeoutException;
28
29 import org.apache.commons.lang.StringUtils;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.eclipse.jetty.client.HttpClient;
33 import org.eclipse.jetty.client.api.ContentResponse;
34 import org.eclipse.jetty.client.api.Request;
35 import org.eclipse.jetty.client.util.StringContentProvider;
36 import org.eclipse.jetty.http.HttpHeader;
37 import org.eclipse.jetty.http.HttpMethod;
38 import org.eclipse.jetty.http.HttpStatus;
39 import org.openhab.binding.innogysmarthome.internal.InnogyBindingConstants;
40 import org.openhab.binding.innogysmarthome.internal.client.entity.StatusResponse;
41 import org.openhab.binding.innogysmarthome.internal.client.entity.action.Action;
42 import org.openhab.binding.innogysmarthome.internal.client.entity.action.ShutterAction;
43 import org.openhab.binding.innogysmarthome.internal.client.entity.action.StateActionSetter;
44 import org.openhab.binding.innogysmarthome.internal.client.entity.capability.Capability;
45 import org.openhab.binding.innogysmarthome.internal.client.entity.capability.CapabilityState;
46 import org.openhab.binding.innogysmarthome.internal.client.entity.device.Device;
47 import org.openhab.binding.innogysmarthome.internal.client.entity.device.DeviceState;
48 import org.openhab.binding.innogysmarthome.internal.client.entity.device.Gateway;
49 import org.openhab.binding.innogysmarthome.internal.client.entity.device.State;
50 import org.openhab.binding.innogysmarthome.internal.client.entity.error.ErrorResponse;
51 import org.openhab.binding.innogysmarthome.internal.client.entity.link.Link;
52 import org.openhab.binding.innogysmarthome.internal.client.entity.location.Location;
53 import org.openhab.binding.innogysmarthome.internal.client.entity.message.Message;
54 import org.openhab.binding.innogysmarthome.internal.client.exception.ApiException;
55 import org.openhab.binding.innogysmarthome.internal.client.exception.AuthenticationException;
56 import org.openhab.binding.innogysmarthome.internal.client.exception.ControllerOfflineException;
57 import org.openhab.binding.innogysmarthome.internal.client.exception.InvalidActionTriggeredException;
58 import org.openhab.binding.innogysmarthome.internal.client.exception.RemoteAccessNotAllowedException;
59 import org.openhab.binding.innogysmarthome.internal.client.exception.ServiceUnavailableException;
60 import org.openhab.binding.innogysmarthome.internal.client.exception.SessionExistsException;
61 import org.openhab.binding.innogysmarthome.internal.client.exception.SessionNotFoundException;
62 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
63 import org.openhab.core.auth.client.oauth2.OAuthClientService;
64 import org.openhab.core.auth.client.oauth2.OAuthException;
65 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 import com.google.gson.Gson;
70 import com.google.gson.GsonBuilder;
71 import com.google.gson.JsonSyntaxException;
72
73 /**
74  * The main client that handles the communication with the innogy SmartHome API service.
75  *
76  * @author Oliver Kuhl - Initial contribution
77  * @author Hilbrand Bouwkamp - Refactored to use openHAB http and oauth2 libraries
78  *
79  */
80 @NonNullByDefault
81 public class InnogyClient {
82     private static final String BEARER = "Bearer ";
83     private static final String CONTENT_TYPE = "application/json";
84     private static final int HTTP_CLIENT_TIMEOUT_SECONDS = 10;
85
86     private final Logger logger = LoggerFactory.getLogger(InnogyClient.class);
87
88     /**
89      * date format as used in json in API. Example: 2016-07-11T10:55:52.3863424Z
90      */
91     private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
92
93     private final Gson gson = new GsonBuilder().setDateFormat(DATE_FORMAT).create();
94     private final OAuthClientService oAuthService;
95     private final HttpClient httpClient;
96     private @Nullable Gateway bridgeDetails;
97     private String configVersion = "";
98
99     public InnogyClient(final OAuthClientService oAuthService, final HttpClient httpClient) {
100         this.oAuthService = oAuthService;
101         this.httpClient = httpClient;
102     }
103
104     /**
105      * @return the bridgeInfo
106      */
107     public @Nullable Gateway getBridgeDetails() {
108         return bridgeDetails;
109     }
110
111     /**
112      * Gets the status
113      *
114      * As the API returns the details of the SmartHome controller (SHC), the data is saved in {@link #bridgeDetails} and
115      * the {@link #configVersion} is set.
116      *
117      * @throws SessionExistsException thrown, if a session already exists
118      * @throws IOException
119      * @throws ApiException
120      */
121     public void refreshStatus() throws IOException, ApiException, AuthenticationException {
122         logger.debug("Get innogy SmartHome status...");
123         final StatusResponse status = executeGet(API_URL_STATUS, StatusResponse.class);
124
125         bridgeDetails = status.gateway;
126         configVersion = bridgeDetails.getConfigVersion();
127
128         logger.debug("innogy SmartHome Status loaded. Configuration version is {}.", configVersion);
129     }
130
131     /**
132      * Executes a HTTP GET request with default headers and returns data as object of type T.
133      *
134      * @param url
135      * @param clazz type of data to return
136      * @return
137      * @throws IOException
138      * @throws AuthenticationException
139      * @throws ApiException
140      */
141     private <T> T executeGet(final String url, final Class<T> clazz)
142             throws IOException, AuthenticationException, ApiException {
143         final ContentResponse response = request(httpClient.newRequest(url).method(HttpMethod.GET));
144
145         return gson.fromJson(response.getContentAsString(), clazz);
146     }
147
148     /**
149      * Executes a HTTP GET request with default headers and returns data as List of type T.
150      *
151      * @param url
152      * @param clazz array type of data to return as list
153      * @throws IOException
154      * @throws AuthenticationException
155      * @throws ApiException
156      */
157     private <T> List<T> executeGetList(final String url, final Class<T[]> clazz)
158             throws IOException, AuthenticationException, ApiException {
159         return Arrays.asList(executeGet(url, clazz));
160     }
161
162     /**
163      * Executes a HTTP POST request with the given {@link Action} as content.
164      *
165      * @param url
166      * @param action
167      * @return
168      * @throws IOException
169      * @throws AuthenticationException
170      * @throws ApiException
171      */
172     private ContentResponse executePost(final String url, final Action action)
173             throws IOException, AuthenticationException, ApiException {
174         final String json = gson.toJson(action);
175         logger.debug("Action {} JSON: {}", action.getType(), json);
176
177         return request(httpClient.newRequest(url).method(HttpMethod.POST)
178                 .content(new StringContentProvider(json), CONTENT_TYPE).accept(CONTENT_TYPE));
179     }
180
181     private ContentResponse request(final Request request) throws IOException, AuthenticationException, ApiException {
182         final ContentResponse response;
183         try {
184             final AccessTokenResponse accessTokenResponse = getAccessTokenResponse();
185
186             response = request.header(HttpHeader.ACCEPT, CONTENT_TYPE)
187                     .header(HttpHeader.AUTHORIZATION, BEARER + accessTokenResponse.getAccessToken())
188                     .timeout(HTTP_CLIENT_TIMEOUT_SECONDS, TimeUnit.SECONDS).send();
189         } catch (InterruptedException | TimeoutException | ExecutionException e) {
190             throw new IOException(e);
191         }
192         handleResponseErrors(response, request.getURI());
193         return response;
194     }
195
196     public AccessTokenResponse getAccessTokenResponse() throws AuthenticationException, IOException {
197         final AccessTokenResponse accessTokenResponse;
198         try {
199             accessTokenResponse = oAuthService.getAccessTokenResponse();
200         } catch (OAuthException | OAuthResponseException e) {
201             throw new AuthenticationException("Error fetching access token: " + e.getMessage());
202         }
203         if (accessTokenResponse == null || StringUtils.isBlank(accessTokenResponse.getAccessToken())) {
204             throw new AuthenticationException("No innogy accesstoken. Is this thing authorized?");
205         }
206         return accessTokenResponse;
207     }
208
209     /**
210      * Handles errors from the {@link ContentResponse} and throws the following errors:
211      *
212      * @param response
213      * @param uri uri of api call made
214      * @throws SessionExistsException
215      * @throws SessionNotFoundException
216      * @throws ControllerOfflineException thrown, if the innogy SmartHome controller (SHC) is offline.
217      * @throws IOException
218      * @throws ApiException
219      * @throws AuthenticationException
220      */
221     private void handleResponseErrors(final ContentResponse response, final URI uri)
222             throws IOException, ApiException, AuthenticationException {
223         String content = "";
224
225         switch (response.getStatus()) {
226             case HttpStatus.OK_200:
227                 logger.debug("Statuscode is OK: [{}]", uri);
228                 return;
229             case HttpStatus.SERVICE_UNAVAILABLE_503:
230                 logger.debug("innogy service is unavailabe (503).");
231                 throw new ServiceUnavailableException("innogy service is unavailabe (503).");
232             default:
233                 logger.debug("Statuscode {} is NOT OK: [{}]", response.getStatus(), uri);
234                 try {
235                     content = response.getContentAsString();
236                     logger.trace("Response error content: {}", content);
237                     final ErrorResponse error = gson.fromJson(content, ErrorResponse.class);
238
239                     if (error == null) {
240                         logger.debug("Error without JSON message, code: {} / message: {}", response.getStatus(),
241                                 response.getReason());
242                         throw new ApiException("Error code: " + response.getStatus());
243                     }
244
245                     switch (error.getCode()) {
246                         case ErrorResponse.ERR_SESSION_EXISTS:
247                             logger.debug("Session exists: {}", error);
248                             throw new SessionExistsException(error.getDescription());
249                         case ErrorResponse.ERR_SESSION_NOT_FOUND:
250                             logger.debug("Session not found: {}", error);
251                             throw new SessionNotFoundException(error.getDescription());
252                         case ErrorResponse.ERR_CONTROLLER_OFFLINE:
253                             logger.debug("Controller offline: {}", error);
254                             throw new ControllerOfflineException(error.getDescription());
255                         case ErrorResponse.ERR_REMOTE_ACCESS_NOT_ALLOWED:
256                             logger.debug(
257                                     "Remote access not allowed. Access is allowed only from the SHC device network.");
258                             throw new RemoteAccessNotAllowedException(
259                                     "Remote access not allowed. Access is allowed only from the SHC device network.");
260                         case ErrorResponse.ERR_INVALID_ACTION_TRIGGERED:
261                             logger.debug("Invalid action triggered. Message: {}", error.getMessages());
262                             throw new InvalidActionTriggeredException(error.getDescription());
263                         default:
264                             logger.debug("Unknown error: {}", error);
265                             throw new ApiException("Unknown error: " + error);
266                     }
267                 } catch (final JsonSyntaxException e) {
268                     throw new ApiException("Invalid JSON syntax in error response: " + content);
269                 }
270         }
271     }
272
273     /**
274      * Sets a new state of a SwitchActuator.
275      *
276      * @param capabilityId
277      * @param state
278      * @throws IOException
279      * @throws ApiException
280      */
281     public void setSwitchActuatorState(final String capabilityId, final boolean state)
282             throws IOException, ApiException, AuthenticationException {
283         executePost(API_URL_ACTION, new StateActionSetter(capabilityId, Capability.TYPE_SWITCHACTUATOR, state));
284     }
285
286     /**
287      * Sets the dimmer level of a DimmerActuator.
288      *
289      * @param capabilityId
290      * @param dimLevel
291      * @throws IOException
292      * @throws ApiException
293      */
294     public void setDimmerActuatorState(final String capabilityId, final int dimLevel)
295             throws IOException, ApiException, AuthenticationException {
296         executePost(API_URL_ACTION, new StateActionSetter(capabilityId, Capability.TYPE_DIMMERACTUATOR, dimLevel));
297     }
298
299     /**
300      * Sets the roller shutter level of a RollerShutterActuator.
301      *
302      * @param capabilityId
303      * @param rollerShutterLevel
304      * @throws IOException
305      * @throws ApiException
306      * @throws AuthenticationException
307      */
308     public void setRollerShutterActuatorState(final String capabilityId, final int rollerShutterLevel)
309             throws IOException, ApiException, AuthenticationException {
310         executePost(API_URL_ACTION,
311                 new StateActionSetter(capabilityId, Capability.TYPE_ROLLERSHUTTERACTUATOR, rollerShutterLevel));
312     }
313
314     /**
315      * Starts or stops moving a RollerShutterActuator
316      *
317      * @param capabilityId
318      * @param rollerShutterAction
319      * @throws IOException
320      * @throws ApiException
321      * @throws AuthenticationException
322      */
323     public void setRollerShutterAction(final String capabilityId,
324             final ShutterAction.ShutterActions rollerShutterAction)
325             throws IOException, ApiException, AuthenticationException {
326         executePost(API_URL_ACTION, new ShutterAction(capabilityId, rollerShutterAction));
327     }
328
329     /**
330      * Sets a new state of a VariableActuator.
331      *
332      * @param capabilityId
333      * @param state
334      * @throws IOException
335      * @throws ApiException
336      */
337     public void setVariableActuatorState(final String capabilityId, final boolean state)
338             throws IOException, ApiException, AuthenticationException {
339         executePost(API_URL_ACTION, new StateActionSetter(capabilityId, Capability.TYPE_VARIABLEACTUATOR, state));
340     }
341
342     /**
343      * Sets the point temperature.
344      *
345      * @param capabilityId
346      * @param pointTemperature
347      * @throws IOException
348      * @throws ApiException
349      */
350     public void setPointTemperatureState(final String capabilityId, final double pointTemperature)
351             throws IOException, ApiException, AuthenticationException {
352         executePost(API_URL_ACTION,
353                 new StateActionSetter(capabilityId, Capability.TYPE_THERMOSTATACTUATOR, pointTemperature));
354     }
355
356     /**
357      * Sets the operation mode to "Auto" or "Manu".
358      *
359      * @param capabilityId
360      * @param autoMode
361      * @throws IOException
362      * @throws ApiException
363      */
364     public void setOperationMode(final String capabilityId, final boolean autoMode)
365             throws IOException, ApiException, AuthenticationException {
366         executePost(API_URL_ACTION,
367                 new StateActionSetter(capabilityId, Capability.TYPE_THERMOSTATACTUATOR,
368                         autoMode ? CapabilityState.STATE_VALUE_OPERATION_MODE_AUTO
369                                 : CapabilityState.STATE_VALUE_OPERATION_MODE_MANUAL));
370     }
371
372     /**
373      * Sets the alarm state.
374      *
375      * @param capabilityId
376      * @param alarmState
377      * @throws IOException
378      * @throws ApiException
379      */
380     public void setAlarmActuatorState(final String capabilityId, final boolean alarmState)
381             throws IOException, ApiException, AuthenticationException {
382         executePost(API_URL_ACTION, new StateActionSetter(capabilityId, Capability.TYPE_ALARMACTUATOR, alarmState));
383     }
384
385     /**
386      * Load the device and returns a {@link List} of {@link Device}s..
387      *
388      * @return List of Devices
389      * @throws IOException
390      * @throws ApiException
391      */
392     public List<Device> getDevices() throws IOException, ApiException, AuthenticationException {
393         logger.debug("Loading innogy devices...");
394         return executeGetList(API_URL_DEVICE, Device[].class);
395     }
396
397     /**
398      * Loads the {@link Device} with the given deviceId.
399      *
400      * @param deviceId
401      * @return
402      * @throws IOException
403      * @throws ApiException
404      */
405     public Device getDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException {
406         logger.debug("Loading device with id {}...", deviceId);
407         return executeGet(API_URL_DEVICE_ID.replace("{id}", deviceId), Device.class);
408     }
409
410     /**
411      * Returns a {@link List} of all {@link Device}s with the full configuration details, {@link Capability}s and
412      * states. Calling this may take a while...
413      *
414      * @return
415      * @throws IOException
416      * @throws ApiException
417      */
418     public List<Device> getFullDevices() throws IOException, ApiException, AuthenticationException {
419         // LOCATIONS
420         final List<Location> locationList = getLocations();
421         final Map<String, Location> locationMap = new HashMap<>();
422         for (final Location l : locationList) {
423             locationMap.put(l.getId(), l);
424         }
425
426         // CAPABILITIES
427         final List<Capability> capabilityList = getCapabilities();
428         final Map<String, Capability> capabilityMap = new HashMap<>();
429         for (final Capability c : capabilityList) {
430             capabilityMap.put(c.getId(), c);
431         }
432
433         // CAPABILITY STATES
434         final List<CapabilityState> capabilityStateList = getCapabilityStates();
435         final Map<String, CapabilityState> capabilityStateMap = new HashMap<>();
436         for (final CapabilityState cs : capabilityStateList) {
437             capabilityStateMap.put(cs.getId(), cs);
438         }
439
440         // DEVICE STATES
441         final List<DeviceState> deviceStateList = getDeviceStates();
442         final Map<String, DeviceState> deviceStateMap = new HashMap<>();
443         for (final DeviceState es : deviceStateList) {
444             deviceStateMap.put(es.getId(), es);
445         }
446
447         // MESSAGES
448         final List<Message> messageList = getMessages();
449         final Map<String, List<Message>> deviceMessageMap = new HashMap<>();
450         for (final Message m : messageList) {
451             if (m.getDevices() != null && !m.getDevices().isEmpty()) {
452                 final String deviceId = m.getDevices().get(0).replace("/device/", "");
453                 List<Message> ml;
454                 if (deviceMessageMap.containsKey(deviceId)) {
455                     ml = deviceMessageMap.get(deviceId);
456                 } else {
457                     ml = new ArrayList<>();
458                 }
459                 ml.add(m);
460                 deviceMessageMap.put(deviceId, ml);
461             }
462         }
463
464         // DEVICES
465         final List<Device> deviceList = getDevices();
466         for (final Device d : deviceList) {
467             if (InnogyBindingConstants.BATTERY_POWERED_DEVICES.contains(d.getType())) {
468                 d.setIsBatteryPowered(true);
469             }
470
471             // location
472             d.setLocation(locationMap.get(d.getLocationId()));
473             final HashMap<String, Capability> deviceCapabilityMap = new HashMap<>();
474
475             // capabilities and their states
476             for (final String cl : d.getCapabilityLinkList()) {
477                 final Capability c = capabilityMap.get(Link.getId(cl));
478                 final String capabilityId = c.getId();
479                 final CapabilityState capabilityState = capabilityStateMap.get(capabilityId);
480                 c.setCapabilityState(capabilityState);
481                 deviceCapabilityMap.put(capabilityId, c);
482             }
483             d.setCapabilityMap(deviceCapabilityMap);
484
485             // device states
486             d.setDeviceState(deviceStateMap.get(d.getId()));
487
488             // messages
489             if (deviceMessageMap.containsKey(d.getId())) {
490                 d.setMessageList(deviceMessageMap.get(d.getId()));
491                 for (final Message m : d.getMessageList()) {
492                     switch (m.getType()) {
493                         case Message.TYPE_DEVICE_LOW_BATTERY:
494                             d.setLowBattery(true);
495                             d.setLowBatteryMessageId(m.getId());
496                             break;
497                     }
498                 }
499             }
500         }
501
502         return deviceList;
503     }
504
505     /**
506      * Returns the {@link Device} with the given deviceId with full configuration details, {@link Capability}s and
507      * states. Calling this may take a little bit longer...
508      *
509      * @param deviceId
510      * @return
511      * @throws IOException
512      * @throws ApiException
513      */
514     public Device getFullDeviceById(final String deviceId) throws IOException, ApiException, AuthenticationException {
515         // LOCATIONS
516         final List<Location> locationList = getLocations();
517         final Map<String, Location> locationMap = new HashMap<>();
518         for (final Location l : locationList) {
519             locationMap.put(l.getId(), l);
520         }
521
522         // CAPABILITIES FOR DEVICE
523         final List<Capability> capabilityList = getCapabilitiesForDevice(deviceId);
524         final Map<String, Capability> capabilityMap = new HashMap<>();
525         for (final Capability c : capabilityList) {
526             capabilityMap.put(c.getId(), c);
527         }
528
529         // CAPABILITY STATES
530         final List<CapabilityState> capabilityStateList = getCapabilityStates();
531         final Map<String, CapabilityState> capabilityStateMap = new HashMap<>();
532         for (final CapabilityState cs : capabilityStateList) {
533             capabilityStateMap.put(cs.getId(), cs);
534         }
535
536         // DEVICE STATE
537         final State state = getDeviceStateByDeviceId(deviceId);
538         final DeviceState deviceState = new DeviceState();
539         deviceState.setId(deviceId);
540         deviceState.setState(state);
541
542         // MESSAGES
543         final List<Message> messageList = getMessages();
544         final List<Message> ml = new ArrayList<>();
545         final String deviceIdPath = "/device/" + deviceId;
546
547         for (final Message m : messageList) {
548             logger.trace("Message Type {} with ID {}", m.getType(), m.getId());
549             if (m.getDevices() != null && !m.getDevices().isEmpty()) {
550                 for (final String li : m.getDevices()) {
551                     if (deviceIdPath.equals(li)) {
552                         ml.add(m);
553                     }
554                 }
555             }
556         }
557
558         // DEVICE
559         final Device d = getDeviceById(deviceId);
560         if (BATTERY_POWERED_DEVICES.contains(d.getType())) {
561             d.setIsBatteryPowered(true);
562             d.setLowBattery(false);
563         }
564
565         // location
566         d.setLocation(locationMap.get(d.getLocationId()));
567
568         // capabilities and their states
569         final HashMap<String, Capability> deviceCapabilityMap = new HashMap<>();
570         for (final String cl : d.getCapabilityLinkList()) {
571
572             final Capability c = capabilityMap.get(Link.getId(cl));
573             c.setCapabilityState(capabilityStateMap.get(c.getId()));
574             deviceCapabilityMap.put(c.getId(), c);
575
576         }
577         d.setCapabilityMap(deviceCapabilityMap);
578
579         // device states
580         d.setDeviceState(deviceState);
581
582         // messages
583         if (!ml.isEmpty()) {
584             d.setMessageList(ml);
585             for (final Message m : d.getMessageList()) {
586                 switch (m.getType()) {
587                     case Message.TYPE_DEVICE_LOW_BATTERY:
588                         d.setLowBattery(true);
589                         d.setLowBatteryMessageId(m.getId());
590                         break;
591                 }
592             }
593         }
594
595         return d;
596     }
597
598     /**
599      * Loads the states for all {@link Device}s.
600      *
601      * @return
602      * @throws IOException
603      * @throws ApiException
604      */
605     public List<DeviceState> getDeviceStates() throws IOException, ApiException, AuthenticationException {
606         logger.debug("Loading device states...");
607         return executeGetList(API_URL_DEVICE_STATES, DeviceState[].class);
608     }
609
610     /**
611      * Loads the device state for the given deviceId.
612      *
613      * @param deviceId
614      * @return
615      * @throws IOException
616      * @throws ApiException
617      */
618     public State getDeviceStateByDeviceId(final String deviceId)
619             throws IOException, ApiException, AuthenticationException {
620         logger.debug("Loading device states for device id {}...", deviceId);
621         return executeGet(API_URL_DEVICE_ID_STATE.replace("{id}", deviceId), State.class);
622     }
623
624     /**
625      * Loads the locations and returns a {@link List} of {@link Location}s.
626      *
627      * @return a List of Devices
628      * @throws IOException
629      * @throws ApiException
630      */
631     public List<Location> getLocations() throws IOException, ApiException, AuthenticationException {
632         logger.debug("Loading locations...");
633         return executeGetList(API_URL_LOCATION, Location[].class);
634     }
635
636     /**
637      * Loads and returns a {@link List} of {@link Capability}s for the given deviceId.
638      *
639      * @param deviceId the id of the {@link Device}
640      * @return
641      * @throws IOException
642      * @throws ApiException
643      */
644     public List<Capability> getCapabilitiesForDevice(final String deviceId)
645             throws IOException, ApiException, AuthenticationException {
646         logger.debug("Loading capabilities for device {}...", deviceId);
647         return executeGetList(API_URL_DEVICE_CAPABILITIES.replace("{id}", deviceId), Capability[].class);
648     }
649
650     /**
651      * Loads and returns a {@link List} of all {@link Capability}s.
652      *
653      * @return
654      * @throws IOException
655      * @throws ApiException
656      */
657     public List<Capability> getCapabilities() throws IOException, ApiException, AuthenticationException {
658         logger.debug("Loading capabilities...");
659         return executeGetList(API_URL_CAPABILITY, Capability[].class);
660     }
661
662     /**
663      * Loads and returns a {@link List} of all {@link Capability}States.
664      *
665      * @return
666      * @throws IOException
667      * @throws ApiException
668      */
669     public List<CapabilityState> getCapabilityStates() throws IOException, ApiException, AuthenticationException {
670         logger.debug("Loading capability states...");
671         return executeGetList(API_URL_CAPABILITY_STATES, CapabilityState[].class);
672     }
673
674     /**
675      * Returns a {@link List} of all {@link Message}s.
676      *
677      * @return
678      * @throws IOException
679      * @throws ApiException
680      */
681     public List<Message> getMessages() throws IOException, ApiException, AuthenticationException {
682         logger.debug("Loading messages...");
683         return executeGetList(API_URL_MESSAGE, Message[].class);
684     }
685
686     /**
687      * @return the configVersion
688      */
689     public String getConfigVersion() {
690         return configVersion;
691     }
692
693     /**
694      * @param configVersion the configVersion to set
695      */
696     public void setConfigVersion(final String configVersion) {
697         this.configVersion = configVersion;
698     }
699 }