]> git.basschouten.com Git - openhab-addons.git/blob
477d68a79459bd6f1cc42ce87d2dc6c4302195ff
[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.evohome.internal.api;
14
15 import java.net.URLEncoder;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Base64;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.concurrent.TimeoutException;
21
22 import org.eclipse.jetty.client.HttpClient;
23 import org.eclipse.jetty.http.HttpMethod;
24 import org.openhab.binding.evohome.internal.api.models.v2.request.HeatSetPoint;
25 import org.openhab.binding.evohome.internal.api.models.v2.request.HeatSetPointBuilder;
26 import org.openhab.binding.evohome.internal.api.models.v2.request.Mode;
27 import org.openhab.binding.evohome.internal.api.models.v2.request.ModeBuilder;
28 import org.openhab.binding.evohome.internal.api.models.v2.response.Authentication;
29 import org.openhab.binding.evohome.internal.api.models.v2.response.Location;
30 import org.openhab.binding.evohome.internal.api.models.v2.response.LocationStatus;
31 import org.openhab.binding.evohome.internal.api.models.v2.response.Locations;
32 import org.openhab.binding.evohome.internal.api.models.v2.response.LocationsStatus;
33 import org.openhab.binding.evohome.internal.api.models.v2.response.UserAccount;
34 import org.openhab.binding.evohome.internal.configuration.EvohomeAccountConfiguration;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Implementation of the evohome client V2 api
40  *
41  * @author Jasper van Zuijlen - Initial contribution
42  *
43  */
44 public class EvohomeApiClient {
45
46     private static final String APPLICATION_ID = "b013aa26-9724-4dbd-8897-048b9aada249";
47     private static final String CLIENT_ID = "4a231089-d2b6-41bd-a5eb-16a0a422b999";
48     private static final String CLIENT_SECRET = "1a15cdb8-42de-407b-add0-059f92c530cb";
49
50     private final Logger logger = LoggerFactory.getLogger(EvohomeApiClient.class);
51     private final EvohomeAccountConfiguration configuration;
52     private final ApiAccess apiAccess;
53
54     private Locations locations = new Locations();
55     private UserAccount useraccount;
56     private LocationsStatus locationsStatus;
57
58     /**
59      * Creates a new API client based on the V2 API interface
60      *
61      * @param configuration The configuration of the account to use
62      */
63     public EvohomeApiClient(EvohomeAccountConfiguration configuration, HttpClient httpClient) {
64         this.configuration = configuration;
65         apiAccess = new ApiAccess(httpClient);
66         apiAccess.setApplicationId(APPLICATION_ID);
67     }
68
69     /**
70      * Closes the current connection to the API
71      */
72     public void close() {
73         apiAccess.setAuthentication(null);
74         useraccount = null;
75         locations = null;
76         locationsStatus = null;
77     }
78
79     public boolean login() {
80         boolean success = authenticateWithUsername();
81
82         // If the authentication succeeded, gather the basic intel as well
83         if (success) {
84             try {
85                 useraccount = requestUserAccount();
86                 locations = requestLocations();
87             } catch (TimeoutException e) {
88                 logger.warn("Timeout while retrieving user and location information. Failing loging.");
89                 success = false;
90             }
91         } else {
92             apiAccess.setAuthentication(null);
93             logger.debug("Authorization failed");
94         }
95
96         return success;
97     }
98
99     public void logout() {
100         close();
101     }
102
103     public void update() {
104         updateAuthentication();
105         try {
106             locationsStatus = requestLocationsStatus();
107         } catch (TimeoutException e) {
108             logger.info("Timeout on update");
109         }
110     }
111
112     public Locations getInstallationInfo() {
113         return locations;
114     }
115
116     public LocationsStatus getInstallationStatus() {
117         return locationsStatus;
118     }
119
120     public void setTcsMode(String tcsId, String mode) throws TimeoutException {
121         String url = String.format(EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_MODE, tcsId);
122         Mode modeCommand = new ModeBuilder().setMode(mode).build();
123         apiAccess.doAuthenticatedPut(url, modeCommand);
124     }
125
126     public void setHeatingZoneOverride(String zoneId, double setPoint) throws TimeoutException {
127         HeatSetPoint setPointCommand = new HeatSetPointBuilder().setSetPoint(setPoint).build();
128         setHeatingZoneOverride(zoneId, setPointCommand);
129     }
130
131     public void cancelHeatingZoneOverride(String zoneId) throws TimeoutException {
132         HeatSetPoint setPointCommand = new HeatSetPointBuilder().setCancelSetPoint().build();
133         setHeatingZoneOverride(zoneId, setPointCommand);
134     }
135
136     private void setHeatingZoneOverride(String zoneId, HeatSetPoint heatSetPoint) throws TimeoutException {
137         String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_HEAT_SETPOINT;
138         url = String.format(url, zoneId);
139         apiAccess.doAuthenticatedPut(url, heatSetPoint);
140     }
141
142     private UserAccount requestUserAccount() throws TimeoutException {
143         String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_ACCOUNT;
144         return apiAccess.doAuthenticatedGet(url, UserAccount.class);
145     }
146
147     private Locations requestLocations() throws TimeoutException {
148         Locations locations = new Locations();
149         if (useraccount != null) {
150             String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_INSTALLATION_INFO;
151             url = String.format(url, useraccount.getUserId());
152
153             locations = apiAccess.doAuthenticatedGet(url, Locations.class);
154         }
155         return locations;
156     }
157
158     private LocationsStatus requestLocationsStatus() throws TimeoutException {
159         LocationsStatus locationsStatus = new LocationsStatus();
160
161         if (locations != null) {
162             for (Location location : locations) {
163                 String url = EvohomeApiConstants.URL_V2_BASE + EvohomeApiConstants.URL_V2_LOCATION_STATUS;
164                 url = String.format(url, location.getLocationInfo().getLocationId());
165                 LocationStatus status = apiAccess.doAuthenticatedGet(url, LocationStatus.class);
166                 locationsStatus.add(status);
167             }
168         }
169         return locationsStatus;
170     }
171
172     private boolean authenticate(String credentials, String grantType) {
173         String data = credentials + "&" + "Host=rs.alarmnet.com%2F&" + "Pragma=no-cache&"
174                 + "Cache-Control=no-store+no-cache&"
175                 + "scope=EMEA-V1-Basic+EMEA-V1-Anonymous+EMEA-V1-Get-Current-User-Account&" + "grant_type=" + grantType
176                 + "&" + "Content-Type=application%2Fx-www-form-urlencoded%3B+charset%3Dutf-8&"
177                 + "Connection=Keep-Alive";
178
179         Map<String, String> headers = new HashMap<>();
180         String basicAuth = Base64.getEncoder().encodeToString((CLIENT_ID + ":" + CLIENT_SECRET).getBytes());
181         headers.put("Authorization", "Basic " + basicAuth);
182         headers.put("Accept", "application/json, application/xml, text/json, text/x-json, text/javascript, text/xml");
183
184         Authentication authentication;
185         try {
186             authentication = apiAccess.doRequest(HttpMethod.POST, EvohomeApiConstants.URL_V2_AUTH, headers, data,
187                     "application/x-www-form-urlencoded", Authentication.class);
188         } catch (TimeoutException e) {
189             // A timeout is not a successful login as well
190             authentication = null;
191         }
192
193         apiAccess.setAuthentication(authentication);
194
195         if (authentication != null) {
196             authentication.setSystemTime(System.currentTimeMillis() / 1000);
197         }
198
199         return (authentication != null);
200     }
201
202     private boolean authenticateWithUsername() {
203         String credentials = "Username=" + URLEncoder.encode(configuration.username, StandardCharsets.UTF_8) + "&"
204                 + "Password=" + URLEncoder.encode(configuration.password, StandardCharsets.UTF_8);
205         return authenticate(credentials, "password");
206     }
207
208     private boolean authenticateWithToken(String accessToken) {
209         String credentials = "refresh_token=" + accessToken;
210         return authenticate(credentials, "refresh_token");
211     }
212
213     private void updateAuthentication() {
214         Authentication authentication = apiAccess.getAuthentication();
215         if (authentication == null) {
216             authenticateWithUsername();
217         } else {
218             // Compare current time to the expiration time minus four intervals for slack
219             long currentTime = System.currentTimeMillis() / 1000;
220             long expiration = authentication.getSystemTime() + authentication.getExpiresIn();
221             expiration -= 4 * configuration.refreshInterval;
222
223             // Update the access token just before it expires, but fall back to username and password
224             // when it fails (i.e. refresh token had been invalidated)
225             if (currentTime > expiration) {
226                 authenticateWithToken(authentication.getRefreshToken());
227                 if (apiAccess.getAuthentication() == null) {
228                     authenticateWithUsername();
229                 }
230             }
231         }
232     }
233 }