2 * Copyright (c) 2010-2022 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.renault.internal.api;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeoutException;
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;
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;
41 * This is a Java version of the python renault-api project developed here:
42 * https://github.com/hacf-fr/renault-api
44 * @author Doug Culnane - Initial contribution
47 public class MyRenaultHttpSession {
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;
59 private final Logger logger = LoggerFactory.getLogger(MyRenaultHttpSession.class);
61 public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) {
63 this.httpClient = httpClient;
64 this.constants = new Constants(config.locale);
67 public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, RenaultUpdateException,
68 RenaultNotImplementedException, InterruptedException, ExecutionException, TimeoutException {
74 final String imageURL = car.getImageURL();
75 if (imageURL == null) {
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()) {
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);
98 } catch (JsonParseException | ClassCastException | IllegalStateException e) {
99 throw new RenaultException("Login Error: cookie value not found in JSON response");
102 logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
103 response.getContentAsString());
104 throw new RenaultException("Login Error: " + response.getReason());
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",
114 if (HttpStatus.OK_200 == response.getStatus()) {
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);
127 } catch (JsonParseException | ClassCastException | IllegalStateException e) {
128 throw new RenaultException(
129 "Get Account Info Error: personId or gigyaDataCenter value not found in JSON response");
132 logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
133 response.getContentAsString());
134 throw new RenaultException("Get Account Info Error: " + response.getReason());
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()) {
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);
154 } catch (JsonParseException | ClassCastException | IllegalStateException e) {
155 throw new RenaultException("Get JWT Error: jwt value not found in JSON response");
158 logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
159 response.getContentAsString());
160 throw new RenaultException("Get JWT Error: " + response.getReason());
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();
177 if (kamereonaccountId == null) {
178 throw new RenaultException("Can not get Kamereon MyRenault Account ID!");
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);
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);
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);
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);
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);
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);
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();
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());
247 throw new RenaultUpdateException(
248 "Kamereon Response Failed! Error: [" + response.getStatus() + "] " + response.getReason());
251 } catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) {
252 logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
257 private String getCountry(RenaultConfiguration config) {
258 String country = "XX";
259 if (config.locale.length() == 5) {
260 country = config.locale.substring(3);