]> git.basschouten.com Git - openhab-addons.git/blob
5733a26206eba466ed4846ea2d0fc3a0e9cc20c7
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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             return gson.fromJson(response, DeviceStatus.class);
141         } catch (IOException | JsonSyntaxException e) {
142             setConnected(false);
143             throw new MelCloudCommException("Error occurred during device status fetch", e);
144         }
145     }
146
147     public DeviceStatus sendDeviceStatus(DeviceStatus deviceStatus) throws MelCloudCommException {
148         assertConnected();
149         String content = gson.toJson(deviceStatus, DeviceStatus.class);
150         logger.debug("Sending device status: {}", content);
151         InputStream data = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
152         try {
153             String response = HttpUtil.executeUrl("POST", DEVICE_URL + "/SetAta", getHeaderProperties(), data,
154                     "application/json", TIMEOUT_MILLISECONDS);
155             logger.debug("Device status sending response: {}", response);
156             return gson.fromJson(response, DeviceStatus.class);
157         } catch (IOException | JsonSyntaxException e) {
158             setConnected(false);
159             throw new MelCloudCommException("Error occurred during device command sending", e);
160         }
161     }
162
163     public HeatpumpDeviceStatus fetchHeatpumpDeviceStatus(int deviceId, int buildingId) throws MelCloudCommException {
164         assertConnected();
165         String url = DEVICE_URL + String.format("/Get?id=%d&buildingID=%d", deviceId, buildingId);
166         try {
167             String response = HttpUtil.executeUrl("GET", url, getHeaderProperties(), null, null, TIMEOUT_MILLISECONDS);
168             logger.debug("Device heatpump status response: {}", response);
169             return gson.fromJson(response, HeatpumpDeviceStatus.class);
170         } catch (IOException | JsonSyntaxException e) {
171             setConnected(false);
172             throw new MelCloudCommException("Error occurred during heatpump device status fetch", e);
173         }
174     }
175
176     public HeatpumpDeviceStatus sendHeatpumpDeviceStatus(HeatpumpDeviceStatus heatpumpDeviceStatus)
177             throws MelCloudCommException {
178         assertConnected();
179         String content = gson.toJson(heatpumpDeviceStatus, HeatpumpDeviceStatus.class);
180         logger.debug("Sending heatpump device status: {}", content);
181         InputStream data = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
182         try {
183             String response = HttpUtil.executeUrl("POST", DEVICE_URL + "/SetAtw", getHeaderProperties(), data,
184                     "application/json", TIMEOUT_MILLISECONDS);
185             logger.debug("Device heatpump status sending response: {}", response);
186             return gson.fromJson(response, HeatpumpDeviceStatus.class);
187         } catch (IOException | JsonSyntaxException e) {
188             setConnected(false);
189             throw new MelCloudCommException("Error occurred during heatpump device command sending", e);
190         }
191     }
192
193     public synchronized boolean isConnected() {
194         return isConnected;
195     }
196
197     private synchronized void setConnected(boolean state) {
198         isConnected = state;
199     }
200
201     private Properties getHeaderProperties() {
202         Properties headers = new Properties();
203         headers.put("X-MitsContextKey", sessionKey);
204         return headers;
205     }
206
207     private void assertConnected() throws MelCloudCommException {
208         if (!isConnected) {
209             throw new MelCloudCommException("Not connected to MELCloud");
210         }
211     }
212 }