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.electroluxair.internal.api;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.TimeoutException;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jetty.client.HttpClient;
21 import org.eclipse.jetty.client.api.ContentResponse;
22 import org.eclipse.jetty.client.api.Request;
23 import org.eclipse.jetty.client.util.StringContentProvider;
24 import org.eclipse.jetty.http.HttpHeader;
25 import org.eclipse.jetty.http.HttpMethod;
26 import org.eclipse.jetty.http.HttpStatus;
27 import org.openhab.binding.electroluxair.internal.ElectroluxAirBridgeConfiguration;
28 import org.openhab.binding.electroluxair.internal.ElectroluxAirException;
29 import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO;
30 import org.openhab.binding.electroluxair.internal.dto.ElectroluxPureA9DTO.AppliancesInfo;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
34 import com.google.gson.Gson;
35 import com.google.gson.JsonArray;
36 import com.google.gson.JsonElement;
37 import com.google.gson.JsonObject;
38 import com.google.gson.JsonParser;
39 import com.google.gson.JsonSyntaxException;
40 import com.google.gson.annotations.SerializedName;
43 * The {@link ElectroluxDeltaAPI} class defines the Elextrolux Delta API
45 * @author Jan Gustafsson - Initial contribution
48 public class ElectroluxDeltaAPI {
49 private static final String CLIENT_URL = "https://electrolux-wellbeing-client.vercel.app/api/mu52m5PR9X";
50 private static final String SERVICE_URL = "https://api.delta.electrolux.com/api/";
51 private static final String JSON_CONTENT_TYPE = "application/json";
52 private static final String LOGIN = "Users/Login";
53 private static final int MAX_RETRIES = 3;
55 private final Logger logger = LoggerFactory.getLogger(ElectroluxDeltaAPI.class);
56 private final Gson gson;
57 private final HttpClient httpClient;
58 private final ElectroluxAirBridgeConfiguration configuration;
59 private String authToken = "";
61 public ElectroluxDeltaAPI(ElectroluxAirBridgeConfiguration configuration, Gson gson, HttpClient httpClient) {
63 this.configuration = configuration;
64 this.httpClient = httpClient;
67 public boolean refresh(Map<String, ElectroluxPureA9DTO> electroluxAirThings) {
72 String json = getAppliances();
73 JsonArray jsonArray = JsonParser.parseString(json).getAsJsonArray();
75 for (JsonElement jsonElement : jsonArray) {
76 String pncId = jsonElement.getAsJsonObject().get("pncId").getAsString();
79 String jsonApplianceInfo = getAppliancesInfo(pncId);
80 AppliancesInfo appliancesInfo = gson.fromJson(jsonApplianceInfo, AppliancesInfo.class);
82 // Get applicance data
83 ElectroluxPureA9DTO dto = getAppliancesData(pncId, ElectroluxPureA9DTO.class);
84 if (appliancesInfo != null) {
85 dto.setApplicancesInfo(appliancesInfo);
87 electroluxAirThings.put(dto.getTwin().getProperties().getReported().deviceId, dto);
90 } catch (ElectroluxAirException e) {
91 logger.warn("Failed to refresh! {}", e.getMessage());
96 public boolean workModePowerOff(String pncId) {
97 String commandJSON = "{ \"WorkMode\": \"PowerOff\" }";
99 return sendCommand(commandJSON, pncId);
100 } catch (ElectroluxAirException e) {
101 logger.warn("Work mode powerOff failed {}", e.getMessage());
106 public boolean workModeAuto(String pncId) {
107 String commandJSON = "{ \"WorkMode\": \"Auto\" }";
109 return sendCommand(commandJSON, pncId);
110 } catch (ElectroluxAirException e) {
111 logger.warn("Work mode auto failed {}", e.getMessage());
116 public boolean workModeManual(String pncId) {
117 String commandJSON = "{ \"WorkMode\": \"Manual\" }";
119 return sendCommand(commandJSON, pncId);
120 } catch (ElectroluxAirException e) {
121 logger.warn("Work mode manual failed {}", e.getMessage());
126 public boolean setFanSpeedLevel(String pncId, int fanSpeedLevel) {
127 if (fanSpeedLevel < 1 && fanSpeedLevel > 10) {
130 String commandJSON = "{ \"Fanspeed\": " + fanSpeedLevel + "}";
132 return sendCommand(commandJSON, pncId);
133 } catch (ElectroluxAirException e) {
134 logger.warn("Work mode manual failed {}", e.getMessage());
140 public boolean setIonizer(String pncId, String ionizerStatus) {
141 String commandJSON = "{ \"Ionizer\": " + ionizerStatus + "}";
143 return sendCommand(commandJSON, pncId);
144 } catch (ElectroluxAirException e) {
145 logger.warn("Work mode manual failed {}", e.getMessage());
150 private void login() throws ElectroluxAirException {
152 Request request = httpClient.newRequest(CLIENT_URL).method(HttpMethod.GET);
154 request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE);
155 request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
157 logger.debug("HTTP GET Request {}.", request.toString());
159 ContentResponse httpResponse = request.send();
160 if (httpResponse.getStatus() != HttpStatus.OK_200) {
161 throw new ElectroluxAirException("Failed to login " + httpResponse.getContentAsString());
163 String json = httpResponse.getContentAsString();
164 JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
165 String clientToken = jsonObject.get("accessToken").getAsString();
167 // Login using ClientToken
168 json = "{ \"Username\": \"" + configuration.username + "\", \"Password\": \"" + configuration.password
170 request = httpClient.newRequest(SERVICE_URL + LOGIN).method(HttpMethod.POST);
171 request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE);
172 request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
173 request.header(HttpHeader.AUTHORIZATION, "Bearer " + clientToken);
174 request.content(new StringContentProvider(json), JSON_CONTENT_TYPE);
176 logger.debug("HTTP POST Request {}.", request.toString());
178 httpResponse = request.send();
179 if (httpResponse.getStatus() != HttpStatus.OK_200) {
180 throw new ElectroluxAirException("Failed to login " + httpResponse.getContentAsString());
183 json = httpResponse.getContentAsString();
184 jsonObject = JsonParser.parseString(json).getAsJsonObject();
185 this.authToken = jsonObject.get("accessToken").getAsString();
186 } catch (InterruptedException | TimeoutException | ExecutionException e) {
187 throw new ElectroluxAirException(e);
191 private String getFromApi(String uri) throws ElectroluxAirException, InterruptedException {
193 for (int i = 0; i < MAX_RETRIES; i++) {
195 Request request = httpClient.newRequest(SERVICE_URL + uri).method(HttpMethod.GET);
196 request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken);
197 request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE);
198 request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
200 ContentResponse response = request.send();
201 String content = response.getContentAsString();
202 logger.trace("API response: {}", content);
204 if (response.getStatus() != HttpStatus.OK_200) {
205 logger.debug("getFromApi failed, HTTP status: {}", response.getStatus());
210 } catch (TimeoutException e) {
211 logger.debug("TimeoutException error in get: {}", e.getMessage());
214 throw new ElectroluxAirException("Failed to fetch from API!");
215 } catch (JsonSyntaxException | ElectroluxAirException | ExecutionException e) {
216 throw new ElectroluxAirException(e);
220 private String getAppliances() throws ElectroluxAirException {
221 String uri = "Domains/Appliances";
223 return getFromApi(uri);
224 } catch (ElectroluxAirException | InterruptedException e) {
225 throw new ElectroluxAirException(e);
229 private String getAppliancesInfo(String pncId) throws ElectroluxAirException {
230 String uri = "AppliancesInfo/" + pncId;
232 return getFromApi(uri);
233 } catch (ElectroluxAirException | InterruptedException e) {
234 throw new ElectroluxAirException(e);
238 private <T> T getAppliancesData(String pncId, Class<T> dto) throws ElectroluxAirException {
239 String uri = "Appliances/" + pncId;
243 json = getFromApi(uri);
244 } catch (ElectroluxAirException | InterruptedException e) {
245 throw new ElectroluxAirException(e);
247 return gson.fromJson(json, dto);
250 private boolean sendCommand(String commandJSON, String pncId) throws ElectroluxAirException {
251 String uri = "Appliances/" + pncId + "/Commands";
253 for (int i = 0; i < MAX_RETRIES; i++) {
255 Request request = httpClient.newRequest(SERVICE_URL + uri).method(HttpMethod.PUT);
256 request.header(HttpHeader.AUTHORIZATION, "Bearer " + authToken);
257 request.header(HttpHeader.ACCEPT, JSON_CONTENT_TYPE);
258 request.header(HttpHeader.CONTENT_TYPE, JSON_CONTENT_TYPE);
259 request.content(new StringContentProvider(commandJSON), JSON_CONTENT_TYPE);
261 ContentResponse response = request.send();
262 String content = response.getContentAsString();
263 logger.trace("API response: {}", content);
265 if (response.getStatus() != HttpStatus.OK_200) {
266 logger.debug("sendCommand failed, HTTP status: {}", response.getStatus());
269 CommandResponseDTO commandResponse = gson.fromJson(content, CommandResponseDTO.class);
270 if (commandResponse != null) {
271 if (commandResponse.code == 200000) {
274 logger.warn("Failed to send command, error code: {}, description: {}",
275 commandResponse.code, commandResponse.codeDescription);
279 logger.warn("Failed to send command, commandResponse is null!");
283 } catch (TimeoutException | InterruptedException e) {
284 logger.warn("TimeoutException error in get");
287 } catch (JsonSyntaxException | ElectroluxAirException | ExecutionException e) {
288 throw new ElectroluxAirException(e);
293 @SuppressWarnings("unused")
294 private static class CommandResponseDTO {
296 public String codeDescription = "";
297 public String information = "";
298 public String message = "";
299 public PayloadDTO payload = new PayloadDTO();
303 private static class PayloadDTO {
304 @SerializedName("Ok")
306 @SerializedName("Response")
307 public ResponseDTO response = new ResponseDTO();
310 private static class ResponseDTO {
311 @SerializedName("Workmode")
312 public String workmode = "";