]> git.basschouten.com Git - openhab-addons.git/blob
2a555f6100bb3917bf5c1698e2ccffe320d7afec
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.renault.internal.api;
14
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeoutException;
17
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.eclipse.jdt.annotation.Nullable;
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.http.HttpMethod;
24 import org.eclipse.jetty.http.HttpStatus;
25 import org.eclipse.jetty.util.Fields;
26 import org.openhab.binding.renault.internal.RenaultConfiguration;
27 import org.openhab.binding.renault.internal.api.exceptions.RenaultException;
28 import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException;
29 import org.openhab.binding.renault.internal.api.exceptions.RenaultNotImplementedException;
30 import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 import com.google.gson.JsonArray;
35 import com.google.gson.JsonElement;
36 import com.google.gson.JsonObject;
37 import com.google.gson.JsonParseException;
38 import com.google.gson.JsonParser;
39
40 /**
41  * This is a Java version of the python renault-api project developed here:
42  * https://github.com/hacf-fr/renault-api
43  *
44  * @author Doug Culnane - Initial contribution
45  */
46 @NonNullByDefault
47 public class MyRenaultHttpSession {
48
49     private RenaultConfiguration config;
50     private HttpClient httpClient;
51     private Constants constants;
52     private @Nullable String kamereonToken;
53     private @Nullable String kamereonaccountId;
54     private @Nullable String cookieValue;
55     private @Nullable String personId;
56     private @Nullable String gigyaDataCenter;
57     private @Nullable String jwt;
58
59     private final Logger logger = LoggerFactory.getLogger(MyRenaultHttpSession.class);
60
61     public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) {
62         this.config = config;
63         this.httpClient = httpClient;
64         this.constants = new Constants(config.locale);
65     }
66
67     public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, RenaultUpdateException,
68             RenaultNotImplementedException, InterruptedException, ExecutionException, TimeoutException {
69         login();
70         getAccountInfo();
71         getJWT();
72         getAccountID();
73
74         final String imageURL = car.getImageURL();
75         if (imageURL == null) {
76             getVehicle(car);
77         }
78     }
79
80     private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException {
81         Fields fields = new Fields();
82         fields.add("ApiKey", this.constants.getGigyaApiKey());
83         fields.add("loginID", config.myRenaultUsername);
84         fields.add("password", config.myRenaultPassword);
85         logger.debug("URL: {}/accounts.login", this.constants.getGigyaRootUrl());
86         ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.login", fields);
87         if (HttpStatus.OK_200 == response.getStatus()) {
88             try {
89                 JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject();
90                 JsonObject sessionInfoJson = responseJson.getAsJsonObject("sessionInfo");
91                 if (sessionInfoJson != null) {
92                     JsonElement element = sessionInfoJson.get("cookieValue");
93                     if (element != null) {
94                         cookieValue = element.getAsString();
95                         logger.debug("Cookie: {}", cookieValue);
96                     }
97                 }
98             } catch (JsonParseException | ClassCastException | IllegalStateException e) {
99                 throw new RenaultException("Login Error: cookie value not found in JSON response");
100             }
101         } else {
102             logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
103                     response.getContentAsString());
104             throw new RenaultException("Login Error: " + response.getReason());
105         }
106     }
107
108     private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException {
109         Fields fields = new Fields();
110         fields.add("ApiKey", this.constants.getGigyaApiKey());
111         fields.add("login_token", cookieValue);
112         ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getAccountInfo",
113                 fields);
114         if (HttpStatus.OK_200 == response.getStatus()) {
115             try {
116                 JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject();
117                 JsonObject dataJson = responseJson.getAsJsonObject("data");
118                 if (dataJson != null) {
119                     JsonElement element1 = dataJson.get("personId");
120                     JsonElement element2 = dataJson.get("gigyaDataCenter");
121                     if (element1 != null && element2 != null) {
122                         personId = element1.getAsString();
123                         gigyaDataCenter = element2.getAsString();
124                         logger.debug("personId ID: {} gigyaDataCenter: {}", personId, gigyaDataCenter);
125                     }
126                 }
127             } catch (JsonParseException | ClassCastException | IllegalStateException e) {
128                 throw new RenaultException(
129                         "Get Account Info Error: personId or gigyaDataCenter value not found in JSON response");
130             }
131         } else {
132             logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
133                     response.getContentAsString());
134             throw new RenaultException("Get Account Info Error: " + response.getReason());
135         }
136     }
137
138     private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException {
139         Fields fields = new Fields();
140         fields.add("ApiKey", this.constants.getGigyaApiKey());
141         fields.add("login_token", cookieValue);
142         fields.add("fields", "data.personId,data.gigyaDataCenter");
143         fields.add("personId", personId);
144         fields.add("gigyaDataCenter", gigyaDataCenter);
145         ContentResponse response = this.httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getJWT", fields);
146         if (HttpStatus.OK_200 == response.getStatus()) {
147             try {
148                 JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject();
149                 JsonElement element = responseJson.get("id_token");
150                 if (element != null) {
151                     jwt = element.getAsString();
152                     logger.debug("jwt: {} ", jwt);
153                 }
154             } catch (JsonParseException | ClassCastException | IllegalStateException e) {
155                 throw new RenaultException("Get JWT Error: jwt value not found in JSON response");
156             }
157         } else {
158             logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
159                     response.getContentAsString());
160             throw new RenaultException("Get JWT Error: " + response.getReason());
161         }
162     }
163
164     private void getAccountID()
165             throws RenaultException, RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
166         JsonObject responseJson = getKamereonResponse(
167                 "/commerce/v1/persons/" + personId + "?country=" + getCountry(config));
168         if (responseJson != null) {
169             JsonArray accounts = responseJson.getAsJsonArray("accounts");
170             for (int i = 0; i < accounts.size(); i++) {
171                 if (accounts.get(i).getAsJsonObject().get("accountType").getAsString().equals("MYRENAULT")) {
172                     kamereonaccountId = accounts.get(i).getAsJsonObject().get("accountId").getAsString();
173                     break;
174                 }
175             }
176         }
177         if (kamereonaccountId == null) {
178             throw new RenaultException("Can not get Kamereon MyRenault Account ID!");
179         }
180     }
181
182     public void getVehicle(Car car)
183             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
184         JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/"
185                 + config.vin + "/details?country=" + getCountry(config));
186         if (responseJson != null) {
187             car.setDetails(responseJson);
188         }
189     }
190
191     public void getBatteryStatus(Car car)
192             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
193         JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId
194                 + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/battery-status?country=" + getCountry(config));
195         if (responseJson != null) {
196             car.setBatteryStatus(responseJson);
197         }
198     }
199
200     public void getHvacStatus(Car car)
201             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
202         JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId
203                 + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/hvac-status?country=" + getCountry(config));
204         if (responseJson != null) {
205             car.setHVACStatus(responseJson);
206         }
207     }
208
209     public void getCockpit(Car car)
210             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
211         JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId
212                 + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/cockpit?country=" + getCountry(config));
213         if (responseJson != null) {
214             car.setCockpit(responseJson);
215         }
216     }
217
218     public void getLocation(Car car)
219             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
220         JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId
221                 + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/location?country=" + getCountry(config));
222         if (responseJson != null) {
223             car.setLocation(responseJson);
224         }
225     }
226
227     private @Nullable JsonObject getKamereonResponse(String path)
228             throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
229         Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET)
230                 .header("Content-type", "application/vnd.api+json").header("apikey", this.constants.getKamereonApiKey())
231                 .header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt);
232         try {
233             ContentResponse response = request.send();
234             if (HttpStatus.OK_200 == response.getStatus()) {
235                 logger.debug("Kamereon Response: {}", response.getContentAsString());
236                 return JsonParser.parseString(response.getContentAsString()).getAsJsonObject();
237             } else {
238                 logger.warn("Kamereon Response: [{}] {} {}", response.getStatus(), response.getReason(),
239                         response.getContentAsString());
240                 if (HttpStatus.FORBIDDEN_403 == response.getStatus()) {
241                     throw new RenaultForbiddenException(
242                             "Kamereon Response Forbidden! Ensure the car is paired in your MyRenault App.");
243                 } else if (HttpStatus.NOT_IMPLEMENTED_501 == response.getStatus()) {
244                     throw new RenaultNotImplementedException(
245                             "Kamereon Service Not Implemented: [" + response.getStatus() + "] " + response.getReason());
246                 } else {
247                     throw new RenaultUpdateException(
248                             "Kamereon Response Failed! Error: [" + response.getStatus() + "] " + response.getReason());
249                 }
250             }
251         } catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) {
252             logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
253         }
254         return null;
255     }
256
257     private String getCountry(RenaultConfiguration config) {
258         String country = "XX";
259         if (config.locale.length() == 5) {
260             country = config.locale.substring(3);
261         }
262         return country;
263     }
264 }