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