]> git.basschouten.com Git - openhab-addons.git/blob
4fc93ad9cf316b461866777f32d710eba515fb31
[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.electroluxair.internal.api;
14
15 import java.time.Instant;
16 import java.util.Map;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jetty.client.HttpClient;
23 import org.eclipse.jetty.client.api.ContentResponse;
24 import org.eclipse.jetty.client.api.Request;
25 import org.eclipse.jetty.client.util.StringContentProvider;
26 import org.eclipse.jetty.http.HttpHeader;
27 import org.eclipse.jetty.http.HttpMethod;
28 import org.eclipse.jetty.http.HttpStatus;
29 import org.openhab.binding.electroluxair.internal.ElectroluxAirBridgeConfiguration;
30 import org.openhab.binding.electroluxair.internal.ElectroluxAirException;
31 import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.gson.Gson;
36 import com.google.gson.JsonObject;
37 import com.google.gson.JsonParser;
38 import com.google.gson.JsonSyntaxException;
39
40 /**
41  * The {@link ElectroluxDeltaAPI} class defines the Elextrolux Delta API
42  *
43  * @author Jan Gustafsson - Initial contribution
44  */
45 @NonNullByDefault
46 public class ElectroluxDeltaAPI {
47     private static final String CLIENT_ID = "ElxOneApp";
48     private static final String CLIENT_SECRET = "8UKrsKD7jH9zvTV7rz5HeCLkit67Mmj68FvRVTlYygwJYy4dW6KF2cVLPKeWzUQUd6KJMtTifFf4NkDnjI7ZLdfnwcPtTSNtYvbP7OzEkmQD9IjhMOf5e1zeAQYtt2yN";
49     private static final String X_API_KEY = "2AMqwEV5MqVhTKrRCyYfVF8gmKrd2rAmp7cUsfky";
50
51     private static final String BASE_URL = "https://api.ocp.electrolux.one";
52     private static final String TOKEN_URL = BASE_URL + "/one-account-authorization/api/v1/token";
53     private static final String AUTHENTICATION_URL = BASE_URL + "/one-account-authentication/api/v1/authenticate";
54     private static final String API_URL = BASE_URL + "/appliance/api/v2";
55     private static final String APPLIANCES_URL = API_URL + "/appliances";
56
57     private static final String JSON_CONTENT_TYPE = "application/json";
58     private static final int MAX_RETRIES = 3;
59     private static final int REQUEST_TIMEOUT_MS = 10_000;
60
61     private final Logger logger = LoggerFactory.getLogger(ElectroluxDeltaAPI.class);
62     private final Gson gson;
63     private final HttpClient httpClient;
64     private final ElectroluxAirBridgeConfiguration configuration;
65     private String authToken = "";
66     private Instant tokenExpiry = Instant.MIN;
67
68     public ElectroluxDeltaAPI(ElectroluxAirBridgeConfiguration configuration, Gson gson, HttpClient httpClient) {
69         this.gson = gson;
70         this.configuration = configuration;
71         this.httpClient = httpClient;
72     }
73
74     public boolean refresh(Map<String, ElectroluxPureA9DTO> electroluxAirThings) {
75         try {
76             if (Instant.now().isAfter(this.tokenExpiry)) {
77                 // Login again since token is expired
78                 login();
79             }
80             // Get all appliances
81             String json = getAppliances();
82             ElectroluxPureA9DTO[] dtos = gson.fromJson(json, ElectroluxPureA9DTO[].class);
83             if (dtos != null) {
84                 for (ElectroluxPureA9DTO dto : dtos) {
85                     String applianceId = dto.getApplianceId();
86                     // Get appliance info
87                     String jsonApplianceInfo = getAppliancesInfo(applianceId);
88                     ElectroluxPureA9DTO.ApplianceInfo applianceInfo = gson.fromJson(jsonApplianceInfo,
89                             ElectroluxPureA9DTO.ApplianceInfo.class);
90                     if (applianceInfo != null) {
91                         if ("AIR_PURIFIER".equals(applianceInfo.getDeviceType())) {
92                             dto.setApplianceInfo(applianceInfo);
93                             electroluxAirThings.put(dto.getProperties().getReported().getDeviceId(), dto);
94                         }
95                     }
96                 }
97                 return true;
98             }
99         } catch (JsonSyntaxException | ElectroluxAirException e) {
100             logger.warn("Failed to refresh! {}", e.getMessage());
101         }
102         return false;
103     }
104
105     public boolean workModePowerOff(String applianceId) {
106         String commandJSON = "{ \"WorkMode\": \"PowerOff\" }";
107         try {
108             return sendCommand(commandJSON, applianceId);
109         } catch (ElectroluxAirException e) {
110             logger.warn("Work mode powerOff failed {}", e.getMessage());
111         }
112         return false;
113     }
114
115     public boolean workModeAuto(String applianceId) {
116         String commandJSON = "{ \"WorkMode\": \"Auto\" }";
117         try {
118             return sendCommand(commandJSON, applianceId);
119         } catch (ElectroluxAirException e) {
120             logger.warn("Work mode auto failed {}", e.getMessage());
121         }
122         return false;
123     }
124
125     public boolean workModeManual(String applianceId) {
126         String commandJSON = "{ \"WorkMode\": \"Manual\" }";
127         try {
128             return sendCommand(commandJSON, applianceId);
129         } catch (ElectroluxAirException e) {
130             logger.warn("Work mode manual failed {}", e.getMessage());
131         }
132         return false;
133     }
134
135     public boolean setFanSpeedLevel(String applianceId, int fanSpeedLevel) {
136         if (fanSpeedLevel < 1 && fanSpeedLevel > 10) {
137             return false;
138         } else {
139             String commandJSON = "{ \"Fanspeed\": " + fanSpeedLevel + "}";
140             try {
141                 return sendCommand(commandJSON, applianceId);
142             } catch (ElectroluxAirException e) {
143                 logger.warn("Work mode manual failed {}", e.getMessage());
144             }
145         }
146         return false;
147     }
148
149     public boolean setIonizer(String applianceId, String ionizerStatus) {
150         String commandJSON = "{ \"Ionizer\": " + ionizerStatus + "}";
151         try {
152             return sendCommand(commandJSON, applianceId);
153         } catch (ElectroluxAirException e) {
154             logger.warn("Work mode manual failed {}", e.getMessage());
155         }
156         return false;
157     }
158
159     public boolean setUILight(String applianceId, String uiLightStatus) {
160         String commandJSON = "{ \"UILight\": " + uiLightStatus + "}";
161         try {
162             return sendCommand(commandJSON, applianceId);
163         } catch (ElectroluxAirException e) {
164             logger.warn("Work mode manual failed {}", e.getMessage());
165         }
166         return false;
167     }
168
169     public boolean setSafetyLock(String applianceId, String safetyLockStatus) {
170         String commandJSON = "{ \"SafetyLock\": " + safetyLockStatus + "}";
171         try {
172             return sendCommand(commandJSON, applianceId);
173         } catch (ElectroluxAirException e) {
174             logger.warn("Work mode manual failed {}", e.getMessage());
175         }
176         return false;
177     }
178
179     private Request createRequest(String uri, HttpMethod httpMethod) {
180         Request request = httpClient.newRequest(uri).method(httpMethod);
181         request.timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
182         request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE);
183         request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
184
185         logger.debug("HTTP POST Request {}.", request.toString());
186
187         return request;
188     }
189
190     private void login() throws ElectroluxAirException {
191         try {
192             String json = "{\"clientId\": \"" + CLIENT_ID + "\", \"clientSecret\": \"" + CLIENT_SECRET
193                     + "\", \"grantType\": \"client_credentials\"}";
194
195             // Fetch ClientToken
196             Request request = createRequest(TOKEN_URL, HttpMethod.POST);
197             request.content(new StringContentProvider(json), JSON_CONTENT_TYPE);
198
199             logger.debug("HTTP POST Request {}.", request.toString());
200
201             ContentResponse httpResponse = request.send();
202             if (httpResponse.getStatus() != HttpStatus.OK_200) {
203                 throw new ElectroluxAirException("Failed to get token 1" + httpResponse.getContentAsString());
204             }
205             json = httpResponse.getContentAsString();
206             logger.trace("Token 1: {}", json);
207             JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
208             String clientToken = jsonObject.get("accessToken").getAsString();
209
210             // Login using access token 1
211             json = "{ \"username\": \"" + configuration.username + "\",  \"password\": \"" + configuration.password
212                     + "\" }";
213             request = createRequest(AUTHENTICATION_URL, HttpMethod.POST);
214             request.header(HttpHeader.AUTHORIZATION, "Bearer " + clientToken);
215             request.header("x-api-key", X_API_KEY);
216
217             request.content(new StringContentProvider(json), JSON_CONTENT_TYPE);
218
219             logger.debug("HTTP POST Request {}.", request.toString());
220
221             httpResponse = request.send();
222             if (httpResponse.getStatus() != HttpStatus.OK_200) {
223                 throw new ElectroluxAirException("Failed to login " + httpResponse.getContentAsString());
224             }
225             json = httpResponse.getContentAsString();
226             logger.trace("Token 2: {}", json);
227             jsonObject = JsonParser.parseString(json).getAsJsonObject();
228             String idToken = jsonObject.get("idToken").getAsString();
229             String countryCode = jsonObject.get("countryCode").getAsString();
230             String credentials = "{\"clientId\": \"" + CLIENT_ID + "\", \"idToken\": \"" + idToken
231                     + "\", \"grantType\": \"urn:ietf:params:oauth:grant-type:token-exchange\"}";
232
233             // Fetch access token 2
234             request = createRequest(TOKEN_URL, HttpMethod.POST);
235             request.header("Origin-Country-Code", countryCode);
236             request.content(new StringContentProvider(credentials), JSON_CONTENT_TYPE);
237
238             logger.debug("HTTP POST Request {}.", request.toString());
239
240             httpResponse = request.send();
241             if (httpResponse.getStatus() != HttpStatus.OK_200) {
242                 throw new ElectroluxAirException("Failed to get token 1" + httpResponse.getContentAsString());
243             }
244
245             // Fetch AccessToken
246             json = httpResponse.getContentAsString();
247             logger.trace("AccessToken: {}", json);
248             jsonObject = JsonParser.parseString(json).getAsJsonObject();
249             this.authToken = jsonObject.get("accessToken").getAsString();
250             int expiresIn = jsonObject.get("expiresIn").getAsInt();
251             this.tokenExpiry = Instant.now().plusSeconds(expiresIn);
252         } catch (InterruptedException | TimeoutException | ExecutionException e) {
253             throw new ElectroluxAirException(e);
254         }
255     }
256
257     private String getFromApi(String uri) throws ElectroluxAirException, InterruptedException {
258         try {
259             for (int i = 0; i < MAX_RETRIES; i++) {
260                 try {
261                     Request request = createRequest(uri, HttpMethod.GET);
262                     request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken);
263                     request.header("x-api-key", X_API_KEY);
264
265                     ContentResponse response = request.send();
266                     String content = response.getContentAsString();
267                     logger.trace("API response: {}", content);
268
269                     if (response.getStatus() != HttpStatus.OK_200) {
270                         logger.debug("getFromApi failed, HTTP status: {}", response.getStatus());
271                         login();
272                     } else {
273                         return content;
274                     }
275                 } catch (TimeoutException e) {
276                     logger.debug("TimeoutException error in get: {}", e.getMessage());
277                 }
278             }
279             throw new ElectroluxAirException("Failed to fetch from API!");
280         } catch (JsonSyntaxException | ElectroluxAirException | ExecutionException e) {
281             throw new ElectroluxAirException(e);
282         }
283     }
284
285     private String getAppliances() throws ElectroluxAirException {
286         try {
287             return getFromApi(APPLIANCES_URL);
288         } catch (ElectroluxAirException | InterruptedException e) {
289             throw new ElectroluxAirException(e);
290         }
291     }
292
293     private String getAppliancesInfo(String applianceId) throws ElectroluxAirException {
294         try {
295             return getFromApi(APPLIANCES_URL + "/" + applianceId + "/info");
296         } catch (ElectroluxAirException | InterruptedException e) {
297             throw new ElectroluxAirException(e);
298         }
299     }
300
301     private boolean sendCommand(String commandJSON, String applianceId) throws ElectroluxAirException {
302         try {
303             for (int i = 0; i < MAX_RETRIES; i++) {
304                 try {
305                     Request request = createRequest(APPLIANCES_URL + "/" + applianceId + "/command", HttpMethod.PUT);
306                     request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken);
307                     request.header("x-api-key", X_API_KEY);
308                     request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
309
310                     ContentResponse response = request.send();
311                     String content = response.getContentAsString();
312                     logger.trace("API response: {}", content);
313
314                     if (response.getStatus() != HttpStatus.OK_200) {
315                         logger.debug("sendCommand failed, HTTP status: {}", response.getStatus());
316                         login();
317                     } else {
318                         return true;
319                     }
320                 } catch (TimeoutException | InterruptedException e) {
321                     logger.warn("TimeoutException error in get");
322                 }
323             }
324         } catch (JsonSyntaxException | ElectroluxAirException | ExecutionException e) {
325             throw new ElectroluxAirException(e);
326         }
327         return false;
328     }
329 }