]> git.basschouten.com Git - openhab-addons.git/blob
6156f9dbdb131415c2ec7b1ead905f6d71641fad
[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.melcloud.internal.api;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.nio.charset.StandardCharsets;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22 import java.util.Properties;
23
24 import org.openhab.binding.melcloud.internal.api.json.Device;
25 import org.openhab.binding.melcloud.internal.api.json.DeviceStatus;
26 import org.openhab.binding.melcloud.internal.api.json.HeatpumpDeviceStatus;
27 import org.openhab.binding.melcloud.internal.api.json.ListDevicesResponse;
28 import org.openhab.binding.melcloud.internal.api.json.LoginClientResponse;
29 import org.openhab.binding.melcloud.internal.exceptions.MelCloudCommException;
30 import org.openhab.binding.melcloud.internal.exceptions.MelCloudLoginException;
31 import org.openhab.core.io.net.http.HttpUtil;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.gson.FieldNamingPolicy;
36 import com.google.gson.Gson;
37 import com.google.gson.GsonBuilder;
38 import com.google.gson.JsonObject;
39 import com.google.gson.JsonSyntaxException;
40
41 /**
42  * The {@link MelCloudConnection} Manage connection to Mitsubishi Cloud (MelCloud).
43  *
44  * @author Luca Calcaterra - Initial Contribution
45  * @author Pauli Anttila - Refactoring
46  * @author Wietse van Buitenen - Return all devices, added heatpump device
47  */
48 public class MelCloudConnection {
49
50     private static final String LOGIN_URL = "https://app.melcloud.com/Mitsubishi.Wifi.Client/Login/ClientLogin";
51     private static final String DEVICE_LIST_URL = "https://app.melcloud.com/Mitsubishi.Wifi.Client/User/ListDevices";
52     private static final String DEVICE_URL = "https://app.melcloud.com/Mitsubishi.Wifi.Client/Device";
53
54     private static final int TIMEOUT_MILLISECONDS = 10000;
55
56     // Gson objects are safe to share across threads and are somewhat expensive to construct. Use a single instance.
57     private static final Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation()
58             .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
59
60     private final Logger logger = LoggerFactory.getLogger(MelCloudConnection.class);
61
62     private boolean isConnected = false;
63     private String sessionKey;
64
65     public void login(String username, String password, int languageId)
66             throws MelCloudCommException, MelCloudLoginException {
67         setConnected(false);
68         sessionKey = null;
69         JsonObject jsonReq = new JsonObject();
70         jsonReq.addProperty("Email", username);
71         jsonReq.addProperty("Password", password);
72         jsonReq.addProperty("Language", languageId);
73         jsonReq.addProperty("AppVersion", "1.17.5.0");
74         jsonReq.addProperty("Persist", false);
75         jsonReq.addProperty("CaptchaResponse", (String) null);
76         InputStream data = new ByteArrayInputStream(jsonReq.toString().getBytes(StandardCharsets.UTF_8));
77
78         try {
79             String loginResponse = HttpUtil.executeUrl("POST", LOGIN_URL, null, data, "application/json",
80                     TIMEOUT_MILLISECONDS);
81             logger.debug("Login response: {}", loginResponse);
82             LoginClientResponse resp = gson.fromJson(loginResponse, LoginClientResponse.class);
83             if (resp.getErrorId() != null) {
84                 String errorMsg = String.format("Login failed, error code: %s", resp.getErrorId());
85                 if (resp.getErrorMessage() != null) {
86                     errorMsg = String.format("%s (%s)", errorMsg, resp.getErrorMessage());
87                 }
88                 throw new MelCloudLoginException(errorMsg);
89             }
90             sessionKey = resp.getLoginData().getContextKey();
91             setConnected(true);
92         } catch (IOException | JsonSyntaxException e) {
93             throw new MelCloudCommException(String.format("Login error, reason: %s", e.getMessage()), e);
94         }
95     }
96
97     public List<Device> fetchDeviceList() throws MelCloudCommException {
98         assertConnected();
99         try {
100             String response = HttpUtil.executeUrl("GET", DEVICE_LIST_URL, getHeaderProperties(), null, null,
101                     TIMEOUT_MILLISECONDS);
102             logger.debug("Device list response: {}", response);
103             List<Device> devices = new ArrayList<>();
104             ListDevicesResponse[] buildings = gson.fromJson(response, ListDevicesResponse[].class);
105             Arrays.asList(buildings).forEach(building -> {
106                 if (building.getStructure().getDevices() != null) {
107                     devices.addAll(building.getStructure().getDevices());
108                 }
109                 building.getStructure().getAreas().forEach(area -> {
110                     if (area.getDevices() != null) {
111                         devices.addAll(area.getDevices());
112                     }
113                 });
114                 building.getStructure().getFloors().forEach(floor -> {
115                     if (floor.getDevices() != null) {
116                         devices.addAll(floor.getDevices());
117                     }
118                     floor.getAreas().forEach(area -> {
119                         if (area.getDevices() != null) {
120                             devices.addAll(area.getDevices());
121                         }
122                     });
123                 });
124             });
125             logger.debug("Found {} devices", devices.size());
126
127             return devices;
128         } catch (IOException | JsonSyntaxException e) {
129             setConnected(false);
130             throw new MelCloudCommException("Error occurred during device list poll", e);
131         }
132     }
133
134     public DeviceStatus fetchDeviceStatus(int deviceId, int buildingId) throws MelCloudCommException {
135         assertConnected();
136         String url = DEVICE_URL + String.format("/Get?id=%d&buildingID=%d", deviceId, buildingId);
137         try {
138             String response = HttpUtil.executeUrl("GET", url, getHeaderProperties(), null, null, TIMEOUT_MILLISECONDS);
139             logger.debug("Device status response: {}", response);
140             DeviceStatus deviceStatus = gson.fromJson(response, DeviceStatus.class);
141             return deviceStatus;
142         } catch (IOException | JsonSyntaxException e) {
143             setConnected(false);
144             throw new MelCloudCommException("Error occurred during device status fetch", e);
145         }
146     }
147
148     public DeviceStatus sendDeviceStatus(DeviceStatus deviceStatus) throws MelCloudCommException {
149         assertConnected();
150         String content = gson.toJson(deviceStatus, DeviceStatus.class);
151         logger.debug("Sending device status: {}", content);
152         InputStream data = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
153         try {
154             String response = HttpUtil.executeUrl("POST", DEVICE_URL + "/SetAta", getHeaderProperties(), data,
155                     "application/json", TIMEOUT_MILLISECONDS);
156             logger.debug("Device status sending response: {}", response);
157             return gson.fromJson(response, DeviceStatus.class);
158         } catch (IOException | JsonSyntaxException e) {
159             setConnected(false);
160             throw new MelCloudCommException("Error occurred during device command sending", e);
161         }
162     }
163
164     public HeatpumpDeviceStatus fetchHeatpumpDeviceStatus(int deviceId, int buildingId) throws MelCloudCommException {
165         assertConnected();
166         String url = DEVICE_URL + String.format("/Get?id=%d&buildingID=%d", deviceId, buildingId);
167         try {
168             String response = HttpUtil.executeUrl("GET", url, getHeaderProperties(), null, null, TIMEOUT_MILLISECONDS);
169             logger.debug("Device heatpump status response: {}", response);
170             HeatpumpDeviceStatus heatpumpDeviceStatus = gson.fromJson(response, HeatpumpDeviceStatus.class);
171             return heatpumpDeviceStatus;
172         } catch (IOException | JsonSyntaxException e) {
173             setConnected(false);
174             throw new MelCloudCommException("Error occurred during heatpump device status fetch", e);
175         }
176     }
177
178     public HeatpumpDeviceStatus sendHeatpumpDeviceStatus(HeatpumpDeviceStatus heatpumpDeviceStatus)
179             throws MelCloudCommException {
180         assertConnected();
181         String content = gson.toJson(heatpumpDeviceStatus, HeatpumpDeviceStatus.class);
182         logger.debug("Sending heatpump device status: {}", content);
183         InputStream data = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
184         try {
185             String response = HttpUtil.executeUrl("POST", DEVICE_URL + "/SetAtw", getHeaderProperties(), data,
186                     "application/json", TIMEOUT_MILLISECONDS);
187             logger.debug("Device heatpump status sending response: {}", response);
188             return gson.fromJson(response, HeatpumpDeviceStatus.class);
189         } catch (IOException | JsonSyntaxException e) {
190             setConnected(false);
191             throw new MelCloudCommException("Error occurred during heatpump device command sending", e);
192         }
193     }
194
195     public synchronized boolean isConnected() {
196         return isConnected;
197     }
198
199     private synchronized void setConnected(boolean state) {
200         isConnected = state;
201     }
202
203     private Properties getHeaderProperties() {
204         Properties headers = new Properties();
205         headers.put("X-MitsContextKey", sessionKey);
206         return headers;
207     }
208
209     private void assertConnected() throws MelCloudCommException {
210         if (!isConnected) {
211             throw new MelCloudCommException("Not connected to MELCloud");
212         }
213     }
214 }