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