diff --git a/bundles/org.openhab.binding.renault/NOTICE b/bundles/org.openhab.binding.renault/NOTICE
new file mode 100644
index 0000000000000..38d625e349232
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/NOTICE
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.renault/README.md b/bundles/org.openhab.binding.renault/README.md
new file mode 100644
index 0000000000000..308d73ac7c102
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/README.md
@@ -0,0 +1,45 @@
+# Renault Binding
+
+This binding allow MyRenault App. users to get battery status and other data from their cars.
+
+A binding that translates the [python based renault-api](https://renault-api.readthedocs.io/en/latest/) in an easy to use binding.
+
+
+## Supported Things
+
+Works on my car (Renault Zoe 50) but I only have one car to test.
+
+
+## Discovery
+
+No discovery
+
+## Binding Configuration
+
+You require your MyRenault credential, locale and VIN for your MyRenault registered car.
+
+## Thing Configuration
+
+The thing has these configuration parameters:
+
+| Parameter | Description | Required |
+|-------------------|----------------------------------------|----------|
+| myRenaultUsername | MyRenault Username. | yes |
+| myRenaultPassword | MyRenault Password. | yes |
+| locale | MyRenault Location (language_country). | yes |
+| vin | Vehicle Identification Number. | yes |
+| refreshInterval | Interval the car is polled in minutes. | yes |
+
+## Channels
+
+Currently all available channels are read only:
+
+| Channel ID | Type | Description |
+|--------------|----------|---------------------------------|
+| batterylevel | Number | State of the battery in % |
+| hvacstatus | Switch | HVAC status switch |
+| image | String | Image URL of MyRenault |
+| location | Location | The GPS position of the vehicle |
+| odometer | Number | Total distance travelled |
+
+
diff --git a/bundles/org.openhab.binding.renault/pom.xml b/bundles/org.openhab.binding.renault/pom.xml
new file mode 100644
index 0000000000000..bbd7abe02f806
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.2.0-SNAPSHOT
+
+
+ org.openhab.binding.renault
+
+ openHAB Add-ons :: Bundles :: Renault Binding
+
+
diff --git a/bundles/org.openhab.binding.renault/src/main/feature/feature.xml b/bundles/org.openhab.binding.renault/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..e443ed5c5c6cb
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.renault/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultBindingConstants.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultBindingConstants.java
new file mode 100644
index 0000000000000..fd22c3f88abe1
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultBindingConstants.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link RenaultBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+@NonNullByDefault
+public class RenaultBindingConstants {
+
+ private static final String BINDING_ID = "renault";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_CAR = new ThingTypeUID(BINDING_ID, "car");
+
+ // List of all Channel ids
+ public static final String CHANNEL_BATTERY_LEVEL = "batterylevel";
+ public static final String CHANNEL_HVAC_STATUS = "hvacstatus";
+ public static final String CHANNEL_IMAGE = "image";
+ public static final String CHANNEL_LOCATION = "location";
+ public static final String CHANNEL_ODOMETER = "odometer";
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultConfiguration.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultConfiguration.java
new file mode 100644
index 0000000000000..1c6d1ba9318ba
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultConfiguration.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal;
+
+/**
+ * The {@link RenaultConfiguration} class contains fields mapping thing configuration parameters.
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+public class RenaultConfiguration {
+
+ public String myRenaultUsername;
+ public String myRenaultPassword;
+ public String locale;
+ public String vin;
+ public int refreshInterval;
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandler.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandler.java
new file mode 100644
index 0000000000000..7f39aa1647b74
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandler.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal;
+
+import static org.openhab.binding.renault.internal.RenaultBindingConstants.*;
+
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.renault.internal.renault.api.Car;
+import org.openhab.binding.renault.internal.renault.api.MyRenaultHttpSession;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PointType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link RenaultHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+@NonNullByDefault
+public class RenaultHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(RenaultHandler.class);
+
+ private @Nullable RenaultConfiguration config;
+
+ private @Nullable ScheduledFuture> pollingJob;
+
+ public RenaultHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ // This binding only polls status data automatically.
+ }
+
+ @Override
+ public void initialize() {
+ updateStatus(ThingStatus.UNKNOWN);
+ this.config = getConfigAs(RenaultConfiguration.class);
+
+ // Background initialization:
+ if (pollingJob == null || pollingJob.isCancelled()) {
+ pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, 0, config.refreshInterval, TimeUnit.MINUTES);
+ }
+ }
+
+ @Override
+ public void dispose() {
+ if (pollingJob != null) {
+ pollingJob.cancel(true);
+ pollingJob = null;
+ }
+ super.dispose();
+ }
+
+ private void getStatus() {
+ MyRenaultHttpSession httpSession;
+ try {
+ httpSession = new MyRenaultHttpSession(this.config);
+ updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
+ httpSession.updateCarData(this.config);
+ updateState(httpSession.getCar());
+ } catch (Exception e) {
+ httpSession = null;
+ logger.error("Error My Renault Http Session.", e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ private void updateState(Car car) {
+ updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(car.batteryLevel));
+ updateState(CHANNEL_HVAC_STATUS, (car.hvacstatus ? OnOffType.ON : OnOffType.OFF));
+ updateState(CHANNEL_IMAGE, new StringType(car.imageURL));
+ updateState(CHANNEL_LOCATION,
+ new PointType(new DecimalType(car.gpsLatitude), new DecimalType(car.gpsLongitude)));
+ updateState(CHANNEL_ODOMETER, new DecimalType(car.odometer));
+ }
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandlerFactory.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandlerFactory.java
new file mode 100644
index 0000000000000..7aa0504672b37
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandlerFactory.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal;
+
+import static org.openhab.binding.renault.internal.RenaultBindingConstants.*;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link RenaultHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.renault", service = ThingHandlerFactory.class)
+public class RenaultHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CAR);
+
+ @Activate
+ public RenaultHandlerFactory() {
+ }
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (THING_TYPE_CAR.equals(thingTypeUID)) {
+ return new RenaultHandler(thing);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Car.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Car.java
new file mode 100644
index 0000000000000..8395ce56fc590
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Car.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal.renault.api;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+/**
+ * @author Doug Culnane - Initial contribution
+ */
+public class Car {
+
+ private final Logger logger = LoggerFactory.getLogger(Car.class);
+
+ public Double batteryLevel;
+ public Boolean hvacstatus;
+ public Double odometer;
+ public String imageURL;
+ public Double gpsLatitude;
+ public Double gpsLongitude;
+
+ public void setBatteryStatus(JsonObject responseJson) {
+ try {
+ batteryLevel = responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject()
+ .get("batteryLevel").getAsDouble();
+ } catch (Exception e) {
+ logger.error("Error {} parsing Battery Status: {}", e, responseJson);
+ }
+ }
+
+ public void setHVACStatus(JsonObject responseJson) {
+ try {
+ hvacstatus = responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject()
+ .get("hvacStatus").getAsString().equals("on");
+ } catch (Exception e) {
+ logger.error("Error {} parsing HVAC Status: {}", e, responseJson);
+ }
+ }
+
+ public void setCockpit(JsonObject responseJson) {
+ try {
+ odometer = responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject()
+ .get("totalMileage").getAsDouble();
+ } catch (Exception e) {
+ logger.error("Error {} parsing Cockpit: {}", e, responseJson);
+ }
+ }
+
+ public void setLocation(JsonObject responseJson) {
+ try {
+ gpsLatitude = responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject()
+ .get("gpsLatitude").getAsDouble();
+ gpsLongitude = responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject()
+ .get("gpsLongitude").getAsDouble();
+ } catch (Exception e) {
+ logger.error("Error {} parsing Cockpit: {}", e, responseJson);
+ }
+ }
+
+ public void setDetails(JsonObject responseJson) {
+ try {
+ JsonArray assetsJson = responseJson.get("assets").getAsJsonArray();
+ for (JsonElement asset : assetsJson) {
+ if (asset.getAsJsonObject().get("assetType").getAsString().equals("PICTURE")) {
+ JsonArray renditions = asset.getAsJsonObject().get("renditions").getAsJsonArray();
+ for (JsonElement rendition : renditions) {
+ if (rendition.getAsJsonObject().get("resolutionType").getAsString()
+ .equals("ONE_MYRENAULT_SMALL")) {
+ imageURL = rendition.getAsJsonObject().get("url").getAsString();
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Error {} parsing Details: {}", e, responseJson);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Constants.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Constants.java
new file mode 100644
index 0000000000000..834e214633fd0
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Constants.java
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal.renault.api;
+
+/**
+ * Constants for Renault API.
+ *
+ * https://github.com/hacf-fr/renault-api/blob/main/src/renault_api/const.py
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+public class Constants {
+
+ private String gigyaApiKey = "gigya-api-key";
+ private String gigyaRootUrl = "gigya-root-url";
+ private String kamereonApiKey = "kamereon-api-key";
+ private String kamereonRootUrl = "kamereon-root-url";
+
+ static private final String GIGYA_URL_EU = "https://accounts.eu1.gigya.com";
+ static private final String GIGYA_URL_US = "https://accounts.us1.gigya.com";
+ static private final String KAMEREON_APIKEY = "Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2";
+ static private final String KAMEREON_URL_EU = "https://api-wired-prod-1-euw1.wrd-aws.com";
+ static private final String KAMEREON_URL_US = "https://api-wired-prod-1-usw2.wrd-aws.com";
+
+ static final String LOCALE_BASE_URL = "https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com";
+
+ public Constants(final String locale) {
+ switch (locale) {
+ case "bg_BG":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3__3ER_6lFvXEXHTP_faLtq6eEdbKDXd9F5GoKwzRyZq37ZQ-db7mXcLzR1Jtls5sn";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "cs_CZ":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_oRlKr5PCVL_sPWUZdJ8c5NOl5Ej8nIZw7VKG7S9Rg36UkDszFzfHfxCaUAUU5or2";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "da_DK":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_5x-2C8b1R4MJPQXkwTPdIqgBpcw653Dakw_ZaEneQRkTBdg9UW9Qg_5G-tMNrTMc";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "de_DE":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_7PLksOyBRkHv126x5WhHb-5pqC1qFR8pQjxSeLB6nhAnPERTUlwnYoznHSxwX668";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "de_AT":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3__B4KghyeUb0GlpU62ZXKrjSfb7CPzwBS368wioftJUL5qXE0Z_sSy0rX69klXuHy";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "de_CH":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_UyiWZs_1UXYCUqK_1n7l7l44UiI_9N9hqwtREV0-UYA_5X7tOV-VKvnGxPBww4q2";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "en_GB":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_e8d4g4SE_Fo8ahyHwwP7ohLGZ79HKNN2T8NjQqoNnk6Epj6ilyYwKdHUyCw3wuxz";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "en_IE":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_Xn7tuOnT9raLEXuwSI1_sFFZNEJhSD0lv3gxkwFtGI-RY4AgiePBiJ9EODh8d9yo";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "es_ES":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_DyMiOwEaxLcPdBTu63Gv3hlhvLaLbW3ufvjHLeuU8U5bx3zx19t5rEKq7KMwk9f1";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "es_MX":
+ gigyaRootUrl = GIGYA_URL_US;
+ gigyaApiKey = "3_BFzR-2wfhMhUs5OCy3R8U8IiQcHS-81vF8bteSe8eFrboMTjEWzbf4pY1aHQ7cW0";
+ kamereonRootUrl = KAMEREON_URL_US;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "fi_FI":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_xSRCLDYhk1SwSeYQLI3DmA8t-etfAfu5un51fws125ANOBZHgh8Lcc4ReWSwaqNY";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "fr_FR":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_4LKbCcMMcvjDm3X89LU4z4mNKYKdl_W0oD9w-Jvih21WqgJKtFZAnb9YdUgWT9_a";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "fr_BE":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_ZK9x38N8pzEvdiG7ojWHeOAAej43APkeJ5Av6VbTkeoOWR4sdkRc-wyF72HzUB8X";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "fr_CH":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_h3LOcrKZ9mTXxMI9clb2R1VGAWPke6jMNqMw4yYLz4N7PGjYyD0hqRgIFAIHusSn";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "fr_LU":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_zt44Wl_wT9mnqn-BHrR19PvXj3wYRPQKLcPbGWawlatFR837KdxSZZStbBTDaqnb";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "hr_HR":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_HcDC5GGZ89NMP1jORLhYNNCcXt7M3thhZ85eGrcQaM2pRwrgrzcIRWEYi_36cFj9";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "hu_HU":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_nGDWrkSGZovhnVFv5hdIxyuuCuJGZfNmlRGp7-5kEn9yb0bfIfJqoDa2opHOd3Mu";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "it_IT":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_js8th3jdmCWV86fKR3SXQWvXGKbHoWFv8NAgRbH7FnIBsi_XvCpN_rtLcI07uNuq";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "it_CH":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_gHkmHaGACxSLKXqD_uDDx415zdTw7w8HXAFyvh0qIP0WxnHPMF2B9K_nREJVSkGq";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "nl_NL":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_ZIOtjqmP0zaHdEnPK7h1xPuBYgtcOyUxbsTY8Gw31Fzy7i7Ltjfm-hhPh23fpHT5";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "nl_BE":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_yachztWczt6i1pIMhLIH9UA6DXK6vXXuCDmcsoA4PYR0g35RvLPDbp49YribFdpC";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "no_NO":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_QrPkEJr69l7rHkdCVls0owC80BB4CGz5xw_b0gBSNdn3pL04wzMBkcwtbeKdl1g9";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "pl_PL":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_2YBjydYRd1shr6bsZdrvA9z7owvSg3W5RHDYDp6AlatXw9hqx7nVoanRn8YGsBN8";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "pt_PT":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3__afxovspi2-Ip1E5kNsAgc4_35lpLAKCF6bq4_xXj2I2bFPjIWxAOAQJlIkreKTD";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "ro_RO":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_WlBp06vVHuHZhiDLIehF8gchqbfegDJADPQ2MtEsrc8dWVuESf2JCITRo5I2CIxs";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "ru_RU":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_N_ecy4iDyoRtX8v5xOxewwZLKXBjRgrEIv85XxI0KJk8AAdYhJIi17LWb086tGXR";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "sk_SK":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_e8d4g4SE_Fo8ahyHwwP7ohLGZ79HKNN2T8NjQqoNnk6Epj6ilyYwKdHUyCw3wuxz";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "sl_SI":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_QKt0ADYxIhgcje4F3fj9oVidHsx3JIIk-GThhdyMMQi8AJR0QoHdA62YArVjbZCt";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ case "sv_SE":
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3_EN5Hcnwanu9_Dqot1v1Aky1YelT5QqG4TxveO0EgKFWZYu03WkeB9FKuKKIWUXIS";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ default:
+ gigyaRootUrl = GIGYA_URL_EU;
+ gigyaApiKey = "3__B4KghyeUb0GlpU62ZXKrjSfb7CPzwBS368wioftJUL5qXE0Z_sSy0rX69klXuHy";
+ kamereonRootUrl = KAMEREON_URL_EU;
+ kamereonApiKey = KAMEREON_APIKEY;
+ break;
+ }
+ }
+
+ public String getGigyaApiKey() {
+ return gigyaApiKey;
+ }
+
+ public String getGigyaRootUrl() {
+ return gigyaRootUrl;
+ }
+
+ public String getKamereonApiKey() {
+ return kamereonApiKey;
+ }
+
+ public String getKamereonRootUrl() {
+ return kamereonRootUrl;
+ }
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/MyRenaultHttpSession.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/MyRenaultHttpSession.java
new file mode 100644
index 0000000000000..39c365577a3a7
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/MyRenaultHttpSession.java
@@ -0,0 +1,271 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.renault.internal.renault.api;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.util.Fields;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.openhab.binding.renault.internal.RenaultConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * Translation of this python code: https://github.com/hacf-fr/renault-api
+ *
+ * @author Doug Culnane - Initial contribution
+ */
+public class MyRenaultHttpSession {
+
+ private HttpClient httpClient;
+ private Constants constants;
+
+ private Car car;
+ private String kamereonToken;
+ private String kamereonaccountId;
+ private String cookieValue;
+ private String personId;
+ private String gigyaDataCenter;
+ private String jwt;
+
+ private final Logger logger = LoggerFactory.getLogger(MyRenaultHttpSession.class);
+
+ public MyRenaultHttpSession(RenaultConfiguration config) throws Exception {
+
+ car = new Car();
+ this.httpClient = new HttpClient(new SslContextFactory(true));
+ this.constants = new Constants(config.locale);
+
+ httpClient.start();
+ login(config);
+ getAccountInfo(config);
+ getJWT(config);
+ getAccountID(config);
+ getVehicle(config);
+ }
+
+ public void updateCarData(RenaultConfiguration config) throws Exception {
+ getCockpit(config);
+ getBatteryStatus(config);
+ getLocation(config);
+ getHvacStatus(config);
+ }
+
+ private void login(RenaultConfiguration config) throws Exception {
+
+ Fields fields = new Fields();
+ fields.add("ApiKey", this.constants.getGigyaApiKey());
+ fields.add("loginID", config.myRenaultUsername);
+ fields.add("password", config.myRenaultPassword);
+
+ logger.debug("URL: {}/accounts.login", this.constants.getGigyaRootUrl());
+ ContentResponse response = this.httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.login", fields);
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ JsonObject sessionInfoJson = responseJson.getAsJsonObject("sessionInfo");
+ cookieValue = sessionInfoJson.get("cookieValue").getAsString();
+ logger.debug("Cookie: {}", cookieValue);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Login Error: " + response.getReason());
+ }
+ }
+
+ private void getAccountInfo(RenaultConfiguration config) throws Exception {
+
+ Fields fields = new Fields();
+ fields.add("ApiKey", this.constants.getGigyaApiKey());
+ fields.add("login_token", cookieValue);
+
+ ContentResponse response = this.httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getAccountInfo",
+ fields);
+
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ JsonObject dataJson = responseJson.getAsJsonObject("data");
+ personId = dataJson.get("personId").getAsString();
+ gigyaDataCenter = dataJson.get("gigyaDataCenter").getAsString();
+ logger.debug("personId ID: {} gigyaDataCenter: {}", personId, gigyaDataCenter);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Account Info Error: " + response.getReason());
+ }
+ }
+
+ private void getJWT(RenaultConfiguration config) throws Exception {
+
+ Fields fields = new Fields();
+ fields.add("ApiKey", this.constants.getGigyaApiKey());
+ fields.add("login_token", cookieValue);
+ fields.add("fields", "data.personId,data.gigyaDataCenter");
+ fields.add("personId", personId);
+ fields.add("gigyaDataCenter", gigyaDataCenter);
+
+ ContentResponse response = this.httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getJWT", fields);
+
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ jwt = responseJson.get("id_token").getAsString();
+ logger.debug("jwt: {} ", jwt);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get JWT Error: " + response.getReason());
+ }
+ }
+
+ private void getAccountID(RenaultConfiguration config) throws Exception {
+
+ Request request = this.httpClient.newRequest(this.constants.getKamereonRootUrl() + "/commerce/v1/persons/"
+ + personId + "?country=" + getCountry(config));
+ request.method(HttpMethod.GET);
+ request.getHeaders().put(new HttpField("Content-type", "application/vnd.api+json"));
+ request.getHeaders().put(new HttpField("apikey", this.constants.getKamereonApiKey()));
+ request.getHeaders().put(new HttpField("x-gigya-id_token", jwt));
+ logger.debug("Kamereon Request: {}", request.getURI().toString());
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ JsonArray accounts = responseJson.getAsJsonArray("accounts");
+ for (int i = 0; i < accounts.size(); i++) {
+ if (accounts.get(i).getAsJsonObject().get("accountType").getAsString().equals("MYRENAULT")) {
+ kamereonaccountId = accounts.get(i).getAsJsonObject().get("accountId").getAsString();
+ }
+ }
+ logger.debug("kamereonaccountId: {} ", kamereonaccountId);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Account ID Error: " + response.getReason());
+ }
+ }
+
+ private void getVehicle(RenaultConfiguration config) throws Exception {
+
+ Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/" + config.vin
+ + "/details?country=" + getCountry(config));
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ logger.debug("responseJson: {} ", responseJson.toString());
+ car.setDetails(responseJson);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Vehicle Error: " + response.getReason());
+ }
+ }
+
+ private void getBatteryStatus(RenaultConfiguration config) throws Exception {
+
+ Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId
+ + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/battery-status?country=" + getCountry(config));
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ logger.debug("responseJson: {} ", responseJson.toString());
+ car.setBatteryStatus(responseJson);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Battery Status Error: " + response.getReason());
+ }
+ }
+
+ private void getHvacStatus(RenaultConfiguration config) throws Exception {
+
+ Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId
+ + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/hvac-status?country=" + getCountry(config));
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ logger.debug("responseJson: {} ", responseJson.toString());
+ car.setHVACStatus(responseJson);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get HVAC Status Error: " + response.getReason());
+ }
+ }
+
+ private void getCockpit(RenaultConfiguration config) throws Exception {
+
+ Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId
+ + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/cockpit?country=" + getCountry(config));
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ logger.debug("responseJson: {} ", responseJson.toString());
+ car.setCockpit(responseJson);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Cockpit Error: " + response.getReason());
+ }
+ }
+
+ private void getLocation(RenaultConfiguration config) throws Exception {
+
+ Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId
+ + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/location?country=" + getCountry(config));
+
+ ContentResponse response = request.send();
+ if (HttpStatus.OK_200 == response.getStatus()) {
+ JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject();
+ logger.debug("responseJson: {} ", responseJson.toString());
+ car.setLocation(responseJson);
+ } else {
+ logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
+ response.getContentAsString());
+ throw new Exception("Get Cockpit Error: " + response.getReason());
+ }
+ }
+
+ private Request getKamereonRequest(String path) {
+ Request request = this.httpClient.newRequest(this.constants.getKamereonRootUrl() + path);
+ request.method(HttpMethod.GET);
+ request.getHeaders().put(new HttpField("Content-type", "application/vnd.api+json"));
+ request.getHeaders().put(new HttpField("apikey", this.constants.getKamereonApiKey()));
+ request.getHeaders().put(new HttpField("x-kamereon-authorization", "Bearer " + kamereonToken));
+ request.getHeaders().put(new HttpField("x-gigya-id_token", jwt));
+ logger.debug("Kamereon Request: {}", request.getURI().toString());
+ return request;
+ }
+
+ private String getCountry(RenaultConfiguration config) {
+ String country = "XX";
+ if (config.locale.length() == 5) {
+ country = config.locale.substring(3);
+ }
+ return country;
+ }
+
+ public Car getCar() {
+ return car;
+ }
+}
diff --git a/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..6f8634020eda3
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ Renault Binding
+ This is the binding for Renault electric cars.
+
+
diff --git a/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644
index 0000000000000..af7444e7a0640
--- /dev/null
+++ b/bundles/org.openhab.binding.renault/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+ Renault Binding for MyRenault registered car
+
+
+
+
+
+
+
+
+
+
+
+
+ MyRenault Username
+
+
+ password
+
+ MyRenault Password
+
+
+
+ MyRenault Location
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vehicle Identification Number
+
+
+
+ Interval the car is polled in minutes.
+ 10
+
+
+
+
+
+
+ Number
+
+ State of the battery in %
+
+
+
+ Switch
+
+ HVAC status
+
+
+
+ String
+
+ Image URL of MyRenault
+
+
+
+ Location
+
+ The GPS position of the vehicle
+
+
+
+ Number
+
+ Total distance travelled
+
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 345fc9daecd35..10e0a0ef38f68 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -282,6 +282,7 @@
org.openhab.binding.regoheatpump
org.openhab.binding.revogi
org.openhab.binding.remoteopenhab
+ org.openhab.binding.renault
org.openhab.binding.resol
org.openhab.binding.rfxcom
org.openhab.binding.rme