From bc4d51651fc3eeb666f7e6082971dcd169522236 Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Thu, 28 Oct 2021 14:51:24 +0200 Subject: [PATCH 01/13] #11465 Initial renualt-api binding Signed-off-by: Doug Culnane Signed-off-by: Doug Culnane --- bundles/org.openhab.binding.renault/NOTICE | 13 + bundles/org.openhab.binding.renault/README.md | 45 +++ bundles/org.openhab.binding.renault/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../internal/RenaultBindingConstants.java | 38 +++ .../internal/RenaultConfiguration.java | 27 ++ .../renault/internal/RenaultHandler.java | 103 +++++++ .../internal/RenaultHandlerFactory.java | 60 ++++ .../renault/internal/renault/api/Car.java | 92 ++++++ .../internal/renault/api/Constants.java | 237 +++++++++++++++ .../renault/api/MyRenaultHttpSession.java | 271 ++++++++++++++++++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../resources/OH-INF/thing/thing-types.xml | 115 ++++++++ bundles/pom.xml | 1 + 14 files changed, 1037 insertions(+) create mode 100644 bundles/org.openhab.binding.renault/NOTICE create mode 100644 bundles/org.openhab.binding.renault/README.md create mode 100644 bundles/org.openhab.binding.renault/pom.xml create mode 100644 bundles/org.openhab.binding.renault/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultBindingConstants.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultConfiguration.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandler.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandlerFactory.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Car.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Constants.java create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/MyRenaultHttpSession.java create mode 100644 bundles/org.openhab.binding.renault/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.renault/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/bundles/org.openhab.binding.renault/NOTICE b/bundles/org.openhab.binding.renault/NOTICE new file mode 100644 index 000000000000..38d625e34923 --- /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 000000000000..308d73ac7c10 --- /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 000000000000..bbd7abe02f80 --- /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 000000000000..e443ed5c5c6c --- /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 000000000000..fd22c3f88abe --- /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 000000000000..1c6d1ba9318b --- /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 000000000000..7f39aa1647b7 --- /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 000000000000..7aa0504672b3 --- /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 000000000000..8395ce56fc59 --- /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 000000000000..834e214633fd --- /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 000000000000..39c365577a3a --- /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 000000000000..6f8634020eda --- /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 000000000000..af7444e7a064 --- /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 345fc9daecd3..10e0a0ef38f6 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 From f93e644de9be01f1d8e50e37703138d59e9d9eed Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Fri, 5 Nov 2021 16:57:18 +0100 Subject: [PATCH 02/13] [renault] Initial Contribution - improved error handling for car data as a result of user testing #11467 Signed-off-by: Doug Culnane --- .../renault/internal/RenaultHandler.java | 22 ++++++++++++++----- .../renault/internal/renault/api/Car.java | 6 +++++ .../renault/api/MyRenaultHttpSession.java | 15 +++++-------- 3 files changed, 27 insertions(+), 16 deletions(-) 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 index 7f39aa1647b7..cf4e1c874f28 100644 --- 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 @@ -93,11 +93,21 @@ private void getStatus() { } 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)); + if (car.batteryLevel != null) { + updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(car.batteryLevel)); + } + if (car.hvacstatus != null) { + updateState(CHANNEL_HVAC_STATUS, (car.hvacstatus ? OnOffType.ON : OnOffType.OFF)); + } + if (car.imageURL != null) { + updateState(CHANNEL_IMAGE, new StringType(car.imageURL)); + } + if (car.gpsLatitude != null && car.gpsLongitude != null) { + updateState(CHANNEL_LOCATION, + new PointType(new DecimalType(car.gpsLatitude), new DecimalType(car.gpsLongitude))); + } + if (car.odometer != null) { + updateState(CHANNEL_ODOMETER, new DecimalType(car.odometer)); + } } } 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 index 8395ce56fc59..3415935c39bf 100644 --- 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 @@ -39,6 +39,7 @@ public void setBatteryStatus(JsonObject responseJson) { .get("batteryLevel").getAsDouble(); } catch (Exception e) { logger.error("Error {} parsing Battery Status: {}", e, responseJson); + batteryLevel = null; } } @@ -48,6 +49,7 @@ public void setHVACStatus(JsonObject responseJson) { .get("hvacStatus").getAsString().equals("on"); } catch (Exception e) { logger.error("Error {} parsing HVAC Status: {}", e, responseJson); + hvacstatus = null; } } @@ -57,6 +59,7 @@ public void setCockpit(JsonObject responseJson) { .get("totalMileage").getAsDouble(); } catch (Exception e) { logger.error("Error {} parsing Cockpit: {}", e, responseJson); + odometer = null; } } @@ -68,6 +71,8 @@ public void setLocation(JsonObject responseJson) { .get("gpsLongitude").getAsDouble(); } catch (Exception e) { logger.error("Error {} parsing Cockpit: {}", e, responseJson); + gpsLatitude = null; + gpsLongitude = null; } } @@ -87,6 +92,7 @@ public void setDetails(JsonObject responseJson) { } } catch (Exception e) { logger.error("Error {} parsing Details: {}", e, responseJson); + imageURL = null; } } } 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 index 39c365577a3a..f72a1fbe5002 100644 --- 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 @@ -172,9 +172,8 @@ private void getVehicle(RenaultConfiguration config) throws Exception { logger.debug("responseJson: {} ", responseJson.toString()); car.setDetails(responseJson); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("Vehicle Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get Vehicle Error: " + response.getReason()); } } @@ -189,9 +188,8 @@ private void getBatteryStatus(RenaultConfiguration config) throws Exception { logger.debug("responseJson: {} ", responseJson.toString()); car.setBatteryStatus(responseJson); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("BatteryStatus Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get Battery Status Error: " + response.getReason()); } } @@ -206,9 +204,8 @@ private void getHvacStatus(RenaultConfiguration config) throws Exception { logger.debug("responseJson: {} ", responseJson.toString()); car.setHVACStatus(responseJson); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("HvacStatus Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get HVAC Status Error: " + response.getReason()); } } @@ -223,9 +220,8 @@ private void getCockpit(RenaultConfiguration config) throws Exception { logger.debug("responseJson: {} ", responseJson.toString()); car.setCockpit(responseJson); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("Cockpit Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get Cockpit Error: " + response.getReason()); } } @@ -240,9 +236,8 @@ private void getLocation(RenaultConfiguration config) throws Exception { logger.debug("responseJson: {} ", responseJson.toString()); car.setLocation(responseJson); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("Location Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get Cockpit Error: " + response.getReason()); } } From fcf730e80ff3b09689267b9a5d18912a51c5cde3 Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Fri, 19 Nov 2021 20:00:31 +0100 Subject: [PATCH 03/13] Add the missing entries in the openhab-addons BOM and CODEOWNERS files. Signed-off-by: Doug Culnane --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index a74a451fe980..e1b8049ec74b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -250,6 +250,7 @@ /bundles/org.openhab.binding.regoheatpump/ @crnjan /bundles/org.openhab.binding.revogi/ @andibraeu /bundles/org.openhab.binding.remoteopenhab/ @lolodomo +/bundles/org.openhab.binding.renault/ @dougculnane /bundles/org.openhab.binding.resol/ @ramack /bundles/org.openhab.binding.rfxcom/ @martinvw @paulianttila /bundles/org.openhab.binding.rme/ @kgoderis diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 67288274817e..9aca3b0e6f2b 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1226,6 +1226,11 @@ org.openhab.binding.remoteopenhab ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.renault + ${project.version} + org.openhab.addons.bundles org.openhab.binding.resol From fc4de46b9b92dd92def3de00520449a59a05d500 Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Sat, 27 Nov 2021 20:54:36 +0100 Subject: [PATCH 04/13] [renault] Pull request feedback fixes. Signed-off-by: Doug Culnane --- bundles/org.openhab.binding.renault/README.md | 26 +-- .../internal/RenaultConfiguration.java | 13 +- .../renault/internal/RenaultHandler.java | 52 ++++- .../internal/RenaultHandlerFactory.java | 10 +- .../renault/internal/renault/api/Car.java | 106 +++++---- .../internal/renault/api/Constants.java | 17 +- .../renault/api/MyRenaultHttpSession.java | 220 +++++++----------- .../api/exceptions/RenaultException.java | 28 +++ .../resources/OH-INF/thing/thing-types.xml | 39 +--- 9 files changed, 272 insertions(+), 239 deletions(-) create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java diff --git a/bundles/org.openhab.binding.renault/README.md b/bundles/org.openhab.binding.renault/README.md index 308d73ac7c10..d6533bf7f11f 100644 --- a/bundles/org.openhab.binding.renault/README.md +++ b/bundles/org.openhab.binding.renault/README.md @@ -7,20 +7,18 @@ A binding that translates the [python based renault-api](https://renault-api.rea ## Supported Things -Works on my car (Renault Zoe 50) but I only have one car to test. +Supports MyRenault registered cars with an active Connected-Services account. + +This binding can only retrieve information that is available in the the MyRenault App. ## 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: +You require your MyRenault credential, locale and VIN for your MyRenault registered car. | Parameter | Description | Required | |-------------------|----------------------------------------|----------| @@ -28,18 +26,18 @@ The thing has these configuration parameters: | myRenaultPassword | MyRenault Password. | yes | | locale | MyRenault Location (language_country). | yes | | vin | Vehicle Identification Number. | yes | -| refreshInterval | Interval the car is polled in minutes. | yes | +| refreshInterval | Interval the car is polled in minutes. | no | ## 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 | +| 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:Length | Total distance travelled | 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 index 1c6d1ba9318b..8040c4241658 100644 --- 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 @@ -12,16 +12,19 @@ */ package org.openhab.binding.renault.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link RenaultConfiguration} class contains fields mapping thing configuration parameters. * * @author Doug Culnane - Initial contribution */ +@NonNullByDefault public class RenaultConfiguration { - public String myRenaultUsername; - public String myRenaultPassword; - public String locale; - public String vin; - public int refreshInterval; + public String myRenaultUsername = ""; + public String myRenaultPassword = ""; + public String locale = ""; + public String vin = ""; + public int refreshInterval = 10; } 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 index cf4e1c874f28..db12c1b8b2ae 100644 --- 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 @@ -13,17 +13,23 @@ package org.openhab.binding.renault.internal; import static org.openhab.binding.renault.internal.RenaultBindingConstants.*; +import static org.openhab.core.library.unit.MetricPrefix.*; +import static org.openhab.core.library.unit.SIUnits.METRE; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import javax.measure.quantity.Length; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; 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.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -45,12 +51,15 @@ public class RenaultHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(RenaultHandler.class); - private @Nullable RenaultConfiguration config; + private RenaultConfiguration config = new RenaultConfiguration(); private @Nullable ScheduledFuture pollingJob; - public RenaultHandler(Thing thing) { + private HttpClient httpClient; + + public RenaultHandler(Thing thing, HttpClient httpClient) { super(thing); + this.httpClient = httpClient; } @Override @@ -63,6 +72,29 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); this.config = getConfigAs(RenaultConfiguration.class); + // Validate configuration + if (this.config.myRenaultUsername.trim().length() == 0) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Username is empty!"); + return; + } + if (this.config.myRenaultPassword.trim().length() == 0) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Password is empty!"); + return; + } + if (this.config.locale.trim().length() == 0) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location is empty!"); + return; + } + if (this.config.vin.trim().length() == 0) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "VIN is empty!"); + return; + } + if (this.config.refreshInterval < 1) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "The refresh interval mush to be larger than 1"); + return; + } + // Background initialization: if (pollingJob == null || pollingJob.isCancelled()) { pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, 0, config.refreshInterval, TimeUnit.MINUTES); @@ -79,17 +111,19 @@ public void dispose() { } private void getStatus() { - MyRenaultHttpSession httpSession; + MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient); try { - httpSession = new MyRenaultHttpSession(this.config); - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE); - httpSession.updateCarData(this.config); - updateState(httpSession.getCar()); + httpSession.initSesssion(); + updateStatus(ThingStatus.ONLINE); } catch (Exception e) { httpSession = null; logger.error("Error My Renault Http Session.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } + if (httpSession != null) { + httpSession.updateCarData(); + updateState(httpSession.getCar()); + } } private void updateState(Car car) { @@ -97,7 +131,7 @@ private void updateState(Car car) { updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(car.batteryLevel)); } if (car.hvacstatus != null) { - updateState(CHANNEL_HVAC_STATUS, (car.hvacstatus ? OnOffType.ON : OnOffType.OFF)); + updateState(CHANNEL_HVAC_STATUS, OnOffType.from(car.hvacstatus)); } if (car.imageURL != null) { updateState(CHANNEL_IMAGE, new StringType(car.imageURL)); @@ -107,7 +141,7 @@ private void updateState(Car car) { new PointType(new DecimalType(car.gpsLatitude), new DecimalType(car.gpsLongitude))); } if (car.odometer != null) { - updateState(CHANNEL_ODOMETER, new DecimalType(car.odometer)); + updateState(CHANNEL_ODOMETER, new QuantityType(car.odometer, KILO(METRE))); } } } 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 index 7aa0504672b3..29cc86a2326c 100644 --- 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 @@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; @@ -25,6 +27,7 @@ import org.openhab.core.thing.binding.ThingHandlerFactory; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; /** * The {@link RenaultHandlerFactory} is responsible for creating things and thing @@ -36,10 +39,13 @@ @Component(configurationPid = "binding.renault", service = ThingHandlerFactory.class) public class RenaultHandlerFactory extends BaseThingHandlerFactory { + private final HttpClient httpClient; + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CAR); @Activate - public RenaultHandlerFactory() { + public RenaultHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); } @Override @@ -52,7 +58,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_CAR.equals(thingTypeUID)) { - return new RenaultHandler(thing); + return new RenaultHandler(thing, httpClient); } 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 index 3415935c39bf..0c99e31b1570 100644 --- 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 @@ -20,79 +20,105 @@ import com.google.gson.JsonObject; /** + * MyRenault registered car for parsing HTTP responses and collecting data and + * information. + * * @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 Double batteryLevel = Double.valueOf(-1); + public Boolean hvacstatus = false; + public Double odometer = Double.valueOf(0); + public String imageURL = ""; + public Double gpsLatitude = Double.valueOf(0); + public Double gpsLongitude = Double.valueOf(0); 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); - batteryLevel = null; + JsonObject attributes = getAttributes(responseJson); + if (attributes != null && attributes.get("batteryLevel") != null) { + batteryLevel = attributes.get("batteryLevel").getAsDouble(); + } + } catch (IllegalStateException | ClassCastException e) { + logger.warn("Error {} parsing Battery Status: {}", e.getMessage(), 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); - hvacstatus = null; + JsonObject attributes = getAttributes(responseJson); + if (attributes != null && attributes.get("hvacStatus") != null) { + hvacstatus = attributes.get("hvacStatus").getAsString().equals("on"); + } + } catch (IllegalStateException | ClassCastException e) { + logger.warn("Error {} parsing HVAC Status: {}", e.getMessage(), 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); - odometer = null; + JsonObject attributes = getAttributes(responseJson); + if (attributes != null && attributes.get("totalMileage") != null) { + odometer = attributes.get("totalMileage").getAsDouble(); + } + } catch (IllegalStateException | ClassCastException e) { + logger.warn("Error {} parsing Cockpit: {}", e.getMessage(), 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); - gpsLatitude = null; - gpsLongitude = null; + JsonObject attributes = getAttributes(responseJson); + if (attributes != null) { + if (attributes.get("gpsLatitude") != null) { + gpsLatitude = attributes.get("gpsLatitude").getAsDouble(); + } + if (attributes.get("gpsLongitude") != null) { + gpsLongitude = attributes.get("gpsLongitude").getAsDouble(); + } + } + } catch (IllegalStateException | ClassCastException e) { + logger.warn("Error {} parsing Cockpit: {}", e.getMessage(), 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(); + imageURL = null; + if (responseJson.get("assets") != null) { + JsonArray assetsJson = responseJson.get("assets").getAsJsonArray(); + for (JsonElement asset : assetsJson) { + if (asset.getAsJsonObject().get("assetType") != null + && asset.getAsJsonObject().get("assetType").getAsString().equals("PICTURE")) { + if (asset.getAsJsonObject().get("renditions") != null) { + JsonArray renditions = asset.getAsJsonObject().get("renditions").getAsJsonArray(); + for (JsonElement rendition : renditions) { + if (rendition.getAsJsonObject().get("resolutionType") != null + && rendition.getAsJsonObject().get("resolutionType").getAsString() + .equals("ONE_MYRENAULT_SMALL")) { + imageURL = rendition.getAsJsonObject().get("url").getAsString(); + break; + } + } } } + if (imageURL != null) { + break; + } } } - } catch (Exception e) { - logger.error("Error {} parsing Details: {}", e, responseJson); - imageURL = null; + } catch (IllegalStateException | ClassCastException e) { + logger.warn("Error {} parsing Details: {}", e.getMessage(), responseJson); + } + } + + private JsonObject getAttributes(JsonObject responseJson) throws IllegalStateException, ClassCastException { + if (responseJson.get("data") != null && responseJson.get("data").getAsJsonObject().get("attributes") != null) { + return responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject(); } + return null; } } 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 index 834e214633fd..3d5678da206b 100644 --- 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 @@ -12,6 +12,8 @@ */ package org.openhab.binding.renault.internal.renault.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Constants for Renault API. * @@ -19,21 +21,20 @@ * * @author Doug Culnane - Initial contribution */ +@NonNullByDefault public class Constants { + private static final String GIGYA_URL_EU = "https://accounts.eu1.gigya.com"; + private static final String GIGYA_URL_US = "https://accounts.us1.gigya.com"; + private static final String KAMEREON_APIKEY = "Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2"; + private static final String KAMEREON_URL_EU = "https://api-wired-prod-1-euw1.wrd-aws.com"; + private static final String KAMEREON_URL_US = "https://api-wired-prod-1-usw2.wrd-aws.com"; + 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": 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 index f72a1fbe5002..eb225497abbf 100644 --- 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 @@ -12,244 +12,202 @@ */ package org.openhab.binding.renault.internal.renault.api; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; 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.openhab.binding.renault.internal.renault.api.exceptions.RenaultException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; /** - * Translation of this python code: https://github.com/hacf-fr/renault-api + * This is a Java version of the python renault-api project developed here: + * https://github.com/hacf-fr/renault-api * * @author Doug Culnane - Initial contribution */ +@NonNullByDefault public class MyRenaultHttpSession { + private Car car = new Car(); + private RenaultConfiguration config; 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 @Nullable Constants constants; + private @Nullable String kamereonToken; + private @Nullable String kamereonaccountId; + private @Nullable String cookieValue; + private @Nullable String personId; + private @Nullable String gigyaDataCenter; + private @Nullable String jwt; private final Logger logger = LoggerFactory.getLogger(MyRenaultHttpSession.class); - public MyRenaultHttpSession(RenaultConfiguration config) throws Exception { + public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) { + this.config = config; + this.httpClient = httpClient; + } - car = new Car(); - this.httpClient = new HttpClient(new SslContextFactory(true)); + public void initSesssion() throws Exception { this.constants = new Constants(config.locale); - httpClient.start(); - login(config); - getAccountInfo(config); - getJWT(config); - getAccountID(config); - getVehicle(config); + login(); + getAccountInfo(); + getJWT(); + getAccountID(); + getVehicle(); } - public void updateCarData(RenaultConfiguration config) throws Exception { - getCockpit(config); - getBatteryStatus(config); - getLocation(config); - getHvacStatus(config); + public void updateCarData() { + getCockpit(); + getBatteryStatus(); + getLocation(); + getHvacStatus(); } - private void login(RenaultConfiguration config) throws Exception { - + private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { 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); + ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.login", fields); if (HttpStatus.OK_200 == response.getStatus()) { - JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject(); + JsonObject responseJson = JsonParser.parseString(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(), + logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Login Error: " + response.getReason()); + throw new RenaultException("Login Error: " + response.getReason()); } } - private void getAccountInfo(RenaultConfiguration config) throws Exception { - + private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { 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", + ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getAccountInfo", fields); - if (HttpStatus.OK_200 == response.getStatus()) { - JsonObject responseJson = new JsonParser().parse(response.getContentAsString()).getAsJsonObject(); + JsonObject responseJson = JsonParser.parseString(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(), + logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get Account Info Error: " + response.getReason()); + throw new RenaultException("Get Account Info Error: " + response.getReason()); } } - private void getJWT(RenaultConfiguration config) throws Exception { - + private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { 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(); + JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); jwt = responseJson.get("id_token").getAsString(); logger.debug("jwt: {} ", jwt); } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), + logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); - throw new Exception("Get JWT Error: " + response.getReason()); + throw new RenaultException("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(); + private void getAccountID() throws RenaultException { + JsonObject responseJson = getKamereonResponse( + "/commerce/v1/persons/" + personId + "?country=" + getCountry(config)); + if (responseJson != null) { 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(); + break; } } - logger.debug("kamereonaccountId: {} ", kamereonaccountId); - } else { - logger.error("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); - throw new Exception("Get Account ID Error: " + response.getReason()); + } + if (kamereonaccountId == null) { + throw new RenaultException("Can not get Kamereon MyRenault Account ID!"); } } - 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()); + private void getVehicle() { + JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/" + + config.vin + "/details?country=" + getCountry(config)); + if (responseJson != null) { car.setDetails(responseJson); - } else { - logger.warn("Vehicle Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); } } - private void getBatteryStatus(RenaultConfiguration config) throws Exception { - - Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId + private void getBatteryStatus() { + JsonObject responseJson = getKamereonResponse("/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()); + if (responseJson != null) { car.setBatteryStatus(responseJson); - } else { - logger.warn("BatteryStatus Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); } } - private void getHvacStatus(RenaultConfiguration config) throws Exception { - - Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId + private void getHvacStatus() { + JsonObject responseJson = getKamereonResponse("/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()); + if (responseJson != null) { car.setHVACStatus(responseJson); - } else { - logger.warn("HvacStatus Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); } } - private void getCockpit(RenaultConfiguration config) throws Exception { - - Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId + private void getCockpit() { + JsonObject responseJson = getKamereonResponse("/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()); + if (responseJson != null) { car.setCockpit(responseJson); - } else { - logger.warn("Cockpit Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); } } - private void getLocation(RenaultConfiguration config) throws Exception { - - Request request = getKamereonRequest("/commerce/v1/accounts/" + kamereonaccountId + private void getLocation() { + JsonObject responseJson = getKamereonResponse("/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()); + if (responseJson != null) { car.setLocation(responseJson); - } else { - logger.warn("Location Response: [{}] {}\n{}", response.getStatus(), response.getReason(), - response.getContentAsString()); } } - 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 @Nullable JsonObject getKamereonResponse(String path) { + Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET) + .header("Content-type", "application/vnd.api+json").header("apikey", this.constants.getKamereonApiKey()) + .header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt); + try { + ContentResponse response = request.send(); + if (HttpStatus.OK_200 == response.getStatus()) { + logger.debug("Kamereon Response: {}", response.getContentAsString()); + return JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); + } else { + logger.warn("Kamereon Response: [{}] {} {}", response.getStatus(), response.getReason(), + response.getContentAsString()); + } + } catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) { + logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage()); + } + return null; } private String getCountry(RenaultConfiguration config) { diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java new file mode 100644 index 000000000000..8e42789276f5 --- /dev/null +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java @@ -0,0 +1,28 @@ +/** + * 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.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * @author Doug Culnane - Initial contribution + */ +@NonNullByDefault +public class RenaultException extends Exception { + + private static final long serialVersionUID = 1L; + + public RenaultException(String message) { + super(message); + } +} 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 index af7444e7a064..d08b62a3b78c 100644 --- 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 @@ -4,38 +4,30 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - - - - Renault Binding for MyRenault registered car + + A MyRenault registered car. - + - + - MyRenault Username password - MyRenault Password - - MyRenault Location + + The language and country combination that best fits with your MyRenault registered car. @@ -72,7 +64,7 @@ Vehicle Identification Number - + Interval the car is polled in minutes. 10 @@ -81,16 +73,9 @@ - - Number - - State of the battery in % - - Switch - - HVAC status + @@ -99,14 +84,8 @@ Image URL of MyRenault - - Location - - The GPS position of the vehicle - - - Number + Number:Length Total distance travelled From 5561b7be7d2a401d4ab121d1c4e2c5a837dab3c7 Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Sat, 27 Nov 2021 21:23:16 +0100 Subject: [PATCH 05/13] [renault] Pull request feedback fixes added non null to the Car. Signed-off-by: Doug Culnane --- .../binding/renault/internal/renault/api/Car.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 index 0c99e31b1570..e509aa9e734c 100644 --- 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 @@ -12,6 +12,8 @@ */ package org.openhab.binding.renault.internal.renault.api; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +27,7 @@ * * @author Doug Culnane - Initial contribution */ +@NonNullByDefault public class Car { private final Logger logger = LoggerFactory.getLogger(Car.class); @@ -87,7 +90,6 @@ public void setLocation(JsonObject responseJson) { public void setDetails(JsonObject responseJson) { try { - imageURL = null; if (responseJson.get("assets") != null) { JsonArray assetsJson = responseJson.get("assets").getAsJsonArray(); for (JsonElement asset : assetsJson) { @@ -105,7 +107,7 @@ public void setDetails(JsonObject responseJson) { } } } - if (imageURL != null) { + if (imageURL.length() > 0) { break; } } @@ -115,7 +117,8 @@ public void setDetails(JsonObject responseJson) { } } - private JsonObject getAttributes(JsonObject responseJson) throws IllegalStateException, ClassCastException { + private @Nullable JsonObject getAttributes(JsonObject responseJson) + throws IllegalStateException, ClassCastException { if (responseJson.get("data") != null && responseJson.get("data").getAsJsonObject().get("attributes") != null) { return responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject(); } From cda7a8d1b836c9b589079175a8e145eea401f771 Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Sun, 28 Nov 2021 20:31:22 +0100 Subject: [PATCH 06/13] [renault] Pull request feedback fixes. Signed-off-by: Doug Culnane --- .../renault/internal/RenaultHandler.java | 8 +-- .../internal/RenaultHandlerFactory.java | 4 +- .../renault/internal/renault/api/Car.java | 16 ++--- .../renault/api/MyRenaultHttpSession.java | 17 +++-- .../api/exceptions/RenaultException.java | 2 + .../resources/OH-INF/thing/thing-types.xml | 66 +++++++++---------- 6 files changed, 59 insertions(+), 54 deletions(-) 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 index db12c1b8b2ae..aefabcf646a3 100644 --- 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 @@ -69,7 +69,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void initialize() { - updateStatus(ThingStatus.UNKNOWN); this.config = getConfigAs(RenaultConfiguration.class); // Validate configuration @@ -94,6 +93,7 @@ public void initialize() { "The refresh interval mush to be larger than 1"); return; } + updateStatus(ThingStatus.UNKNOWN); // Background initialization: if (pollingJob == null || pollingJob.isCancelled()) { @@ -117,7 +117,7 @@ private void getStatus() { updateStatus(ThingStatus.ONLINE); } catch (Exception e) { httpSession = null; - logger.error("Error My Renault Http Session.", e); + logger.warn("Error My Renault Http Session.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } if (httpSession != null) { @@ -133,7 +133,7 @@ private void updateState(Car car) { if (car.hvacstatus != null) { updateState(CHANNEL_HVAC_STATUS, OnOffType.from(car.hvacstatus)); } - if (car.imageURL != null) { + if (car.imageURL != null && !car.imageURL.isEmpty()) { updateState(CHANNEL_IMAGE, new StringType(car.imageURL)); } if (car.gpsLatitude != null && car.gpsLongitude != null) { @@ -141,7 +141,7 @@ private void updateState(Car car) { new PointType(new DecimalType(car.gpsLatitude), new DecimalType(car.gpsLongitude))); } if (car.odometer != null) { - updateState(CHANNEL_ODOMETER, new QuantityType(car.odometer, KILO(METRE))); + updateState(CHANNEL_ODOMETER, new QuantityType(Double.valueOf(car.odometer), KILO(METRE))); } } } 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 index 29cc86a2326c..73dcdea8b084 100644 --- 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 @@ -39,10 +39,10 @@ @Component(configurationPid = "binding.renault", service = ThingHandlerFactory.class) public class RenaultHandlerFactory extends BaseThingHandlerFactory { - private final HttpClient httpClient; - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CAR); + private final HttpClient httpClient; + @Activate public RenaultHandlerFactory(final @Reference HttpClientFactory httpClientFactory) { this.httpClient = httpClientFactory.getCommonHttpClient(); 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 index e509aa9e734c..f364ee97bc2f 100644 --- 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 @@ -32,12 +32,12 @@ public class Car { private final Logger logger = LoggerFactory.getLogger(Car.class); - public Double batteryLevel = Double.valueOf(-1); - public Boolean hvacstatus = false; - public Double odometer = Double.valueOf(0); - public String imageURL = ""; - public Double gpsLatitude = Double.valueOf(0); - public Double gpsLongitude = Double.valueOf(0); + public @Nullable Double batteryLevel; + public @Nullable Boolean hvacstatus; + public @Nullable Double odometer; + public @Nullable String imageURL; + public @Nullable Double gpsLatitude; + public @Nullable Double gpsLongitude; public void setBatteryStatus(JsonObject responseJson) { try { @@ -84,7 +84,7 @@ public void setLocation(JsonObject responseJson) { } } } catch (IllegalStateException | ClassCastException e) { - logger.warn("Error {} parsing Cockpit: {}", e.getMessage(), responseJson); + logger.warn("Error {} parsing Location: {}", e.getMessage(), responseJson); } } @@ -107,7 +107,7 @@ public void setDetails(JsonObject responseJson) { } } } - if (imageURL.length() > 0) { + if (!imageURL.isEmpty()) { break; } } 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 index eb225497abbf..dac35448d41d 100644 --- 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 @@ -45,7 +45,7 @@ public class MyRenaultHttpSession { private Car car = new Car(); private RenaultConfiguration config; private HttpClient httpClient; - private @Nullable Constants constants; + private Constants constants; private @Nullable String kamereonToken; private @Nullable String kamereonaccountId; private @Nullable String cookieValue; @@ -58,11 +58,11 @@ public class MyRenaultHttpSession { public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) { this.config = config; this.httpClient = httpClient; + this.constants = new Constants(config.locale); } - public void initSesssion() throws Exception { - this.constants = new Constants(config.locale); - httpClient.start(); + public void initSesssion() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, + JsonParseException, ClassCastException, IllegalStateException, NullPointerException { login(); getAccountInfo(); getJWT(); @@ -77,7 +77,8 @@ public void updateCarData() { getHvacStatus(); } - private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { + private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, + JsonParseException, ClassCastException, IllegalStateException, NullPointerException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("loginID", config.myRenaultUsername); @@ -96,7 +97,8 @@ private void login() throws RenaultException, InterruptedException, ExecutionExc } } - private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { + private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, + JsonParseException, ClassCastException, IllegalStateException, NullPointerException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("login_token", cookieValue); @@ -115,7 +117,8 @@ private void getAccountInfo() throws RenaultException, InterruptedException, Exe } } - private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { + private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, + JsonParseException, ClassCastException, IllegalStateException, NullPointerException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("login_token", cookieValue); diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java index 8e42789276f5..9bc2e5123a77 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java @@ -15,6 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** + * Exception thrown while trying to access the My Renault web services. + * * @author Doug Culnane - Initial contribution */ @NonNullByDefault 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 index d08b62a3b78c..ee21b82bbcd7 100644 --- 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 @@ -26,45 +26,45 @@ - - The language and country combination that best fits with your MyRenault registered car. + + The country (and language combination) that best fits with your MyRenault registered car. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vehicle Identification Number - + Interval the car is polled in minutes. 10 @@ -88,7 +88,7 @@ Number:Length Total distance travelled - + From b91b0c9babde4e9875385b3f5dba4a48beea971f Mon Sep 17 00:00:00 2001 From: Doug Culnane Date: Sun, 28 Nov 2021 20:40:57 +0100 Subject: [PATCH 07/13] [renault] Pull request feedback fixes. Signed-off-by: Doug Culnane --- .../openhab/binding/renault/internal/RenaultHandler.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index aefabcf646a3..8f3d15ca91ef 100644 --- 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 @@ -72,19 +72,19 @@ public void initialize() { this.config = getConfigAs(RenaultConfiguration.class); // Validate configuration - if (this.config.myRenaultUsername.trim().length() == 0) { + if (this.config.myRenaultUsername.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Username is empty!"); return; } - if (this.config.myRenaultPassword.trim().length() == 0) { + if (this.config.myRenaultPassword.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Password is empty!"); return; } - if (this.config.locale.trim().length() == 0) { + if (this.config.locale.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location is empty!"); return; } - if (this.config.vin.trim().length() == 0) { + if (this.config.vin.isBlank()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "VIN is empty!"); return; } From ebc7f9415098a2b2c641398f87f0dadfba7ab68f Mon Sep 17 00:00:00 2001 From: Laurent Garnier Date: Sun, 28 Nov 2021 23:08:05 +0100 Subject: [PATCH 08/13] Proposal of enhancements Signed-off-by: Laurent Garnier --- .../internal/RenaultHandlerFactory.java | 1 + .../internal/{renault => }/api/Car.java | 10 +-- .../internal/{renault => }/api/Constants.java | 2 +- .../api/MyRenaultHttpSession.java | 68 +++++++++++++------ .../api/exceptions/RenaultException.java | 2 +- .../{ => handler}/RenaultHandler.java | 43 +++++++----- 6 files changed, 81 insertions(+), 45 deletions(-) rename bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/{renault => }/api/Car.java (94%) rename bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/{renault => }/api/Constants.java (99%) rename bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/{renault => }/api/MyRenaultHttpSession.java (76%) rename bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/{renault => }/api/exceptions/RenaultException.java (91%) rename bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/{ => handler}/RenaultHandler.java (76%) 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 index 73dcdea8b084..7561d811cb6e 100644 --- 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 @@ -19,6 +19,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.renault.internal.handler.RenaultHandler; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; 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/api/Car.java similarity index 94% rename from bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Car.java rename to bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java index f364ee97bc2f..8819942bf046 100644 --- 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/api/Car.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.renault.internal.renault.api; +package org.openhab.binding.renault.internal.api; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -24,7 +24,7 @@ /** * MyRenault registered car for parsing HTTP responses and collecting data and * information. - * + * * @author Doug Culnane - Initial contribution */ @NonNullByDefault @@ -92,6 +92,7 @@ public void setDetails(JsonObject responseJson) { try { if (responseJson.get("assets") != null) { JsonArray assetsJson = responseJson.get("assets").getAsJsonArray(); + String url = null; for (JsonElement asset : assetsJson) { if (asset.getAsJsonObject().get("assetType") != null && asset.getAsJsonObject().get("assetType").getAsString().equals("PICTURE")) { @@ -101,13 +102,14 @@ public void setDetails(JsonObject responseJson) { if (rendition.getAsJsonObject().get("resolutionType") != null && rendition.getAsJsonObject().get("resolutionType").getAsString() .equals("ONE_MYRENAULT_SMALL")) { - imageURL = rendition.getAsJsonObject().get("url").getAsString(); + url = rendition.getAsJsonObject().get("url").getAsString(); break; } } } } - if (!imageURL.isEmpty()) { + if (url != null && !url.isEmpty()) { + imageURL = url; break; } } 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/api/Constants.java similarity index 99% rename from bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/Constants.java rename to bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Constants.java index 3d5678da206b..45170ba9522a 100644 --- 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/api/Constants.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.renault.internal.renault.api; +package org.openhab.binding.renault.internal.api; import org.eclipse.jdt.annotation.NonNullByDefault; 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/api/MyRenaultHttpSession.java similarity index 76% rename from bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/MyRenaultHttpSession.java rename to bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java index dac35448d41d..00955767892d 100644 --- 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/api/MyRenaultHttpSession.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.renault.internal.renault.api; +package org.openhab.binding.renault.internal.api; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -24,11 +24,12 @@ import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.util.Fields; import org.openhab.binding.renault.internal.RenaultConfiguration; -import org.openhab.binding.renault.internal.renault.api.exceptions.RenaultException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @@ -61,8 +62,7 @@ public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) this.constants = new Constants(config.locale); } - public void initSesssion() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, - JsonParseException, ClassCastException, IllegalStateException, NullPointerException { + public void initSesssion() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { login(); getAccountInfo(); getJWT(); @@ -77,8 +77,7 @@ public void updateCarData() { getHvacStatus(); } - private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, - JsonParseException, ClassCastException, IllegalStateException, NullPointerException { + private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("loginID", config.myRenaultUsername); @@ -86,10 +85,19 @@ private void login() throws RenaultException, InterruptedException, ExecutionExc logger.debug("URL: {}/accounts.login", this.constants.getGigyaRootUrl()); ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.login", fields); if (HttpStatus.OK_200 == response.getStatus()) { - JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); - JsonObject sessionInfoJson = responseJson.getAsJsonObject("sessionInfo"); - cookieValue = sessionInfoJson.get("cookieValue").getAsString(); - logger.debug("Cookie: {}", cookieValue); + try { + JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); + JsonObject sessionInfoJson = responseJson.getAsJsonObject("sessionInfo"); + if (sessionInfoJson != null) { + JsonElement element = sessionInfoJson.get("cookieValue"); + if (element != null) { + cookieValue = element.getAsString(); + logger.debug("Cookie: {}", cookieValue); + } + } + } catch (JsonParseException | ClassCastException | IllegalStateException e) { + throw new RenaultException("Login Error: cookie value not found in JSON response"); + } } else { logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); @@ -97,19 +105,29 @@ private void login() throws RenaultException, InterruptedException, ExecutionExc } } - private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, - JsonParseException, ClassCastException, IllegalStateException, NullPointerException { + private void getAccountInfo() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("login_token", cookieValue); ContentResponse response = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getAccountInfo", fields); if (HttpStatus.OK_200 == response.getStatus()) { - JsonObject responseJson = JsonParser.parseString(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); + try { + JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); + JsonObject dataJson = responseJson.getAsJsonObject("data"); + if (dataJson != null) { + JsonElement element1 = dataJson.get("personId"); + JsonElement element2 = dataJson.get("gigyaDataCenter"); + if (element1 != null && element2 != null) { + personId = element1.getAsString(); + gigyaDataCenter = element2.getAsString(); + logger.debug("personId ID: {} gigyaDataCenter: {}", personId, gigyaDataCenter); + } + } + } catch (JsonParseException | ClassCastException | IllegalStateException e) { + throw new RenaultException( + "Get Account Info Error: personId or gigyaDataCenter value not found in JSON response"); + } } else { logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); @@ -117,8 +135,7 @@ private void getAccountInfo() throws RenaultException, InterruptedException, Exe } } - private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException, - JsonParseException, ClassCastException, IllegalStateException, NullPointerException { + private void getJWT() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { Fields fields = new Fields(); fields.add("ApiKey", this.constants.getGigyaApiKey()); fields.add("login_token", cookieValue); @@ -127,9 +144,16 @@ private void getJWT() throws RenaultException, InterruptedException, ExecutionEx fields.add("gigyaDataCenter", gigyaDataCenter); ContentResponse response = this.httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.getJWT", fields); if (HttpStatus.OK_200 == response.getStatus()) { - JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); - jwt = responseJson.get("id_token").getAsString(); - logger.debug("jwt: {} ", jwt); + try { + JsonObject responseJson = JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); + JsonElement element = responseJson.get("id_token"); + if (element != null) { + jwt = element.getAsString(); + logger.debug("jwt: {} ", jwt); + } + } catch (JsonParseException | ClassCastException | IllegalStateException e) { + throw new RenaultException("Get JWT Error: jwt value not found in JSON response"); + } } else { logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(), response.getContentAsString()); diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultException.java similarity index 91% rename from bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java rename to bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultException.java index 9bc2e5123a77..bb8385a48331 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/renault/api/exceptions/RenaultException.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultException.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.renault.internal.renault.api.exceptions; +package org.openhab.binding.renault.internal.api.exceptions; import org.eclipse.jdt.annotation.NonNullByDefault; 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/handler/RenaultHandler.java similarity index 76% rename from bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/RenaultHandler.java rename to bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java index 8f3d15ca91ef..b849d1e7b13e 100644 --- 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/handler/RenaultHandler.java @@ -10,10 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.renault.internal; +package org.openhab.binding.renault.internal.handler; import static org.openhab.binding.renault.internal.RenaultBindingConstants.*; -import static org.openhab.core.library.unit.MetricPrefix.*; +import static org.openhab.core.library.unit.MetricPrefix.KILO; import static org.openhab.core.library.unit.SIUnits.METRE; import java.util.concurrent.ScheduledFuture; @@ -24,8 +24,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.renault.internal.renault.api.Car; -import org.openhab.binding.renault.internal.renault.api.MyRenaultHttpSession; +import org.openhab.binding.renault.internal.RenaultConfiguration; +import org.openhab.binding.renault.internal.api.Car; +import org.openhab.binding.renault.internal.api.MyRenaultHttpSession; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PointType; @@ -96,15 +97,17 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); // Background initialization: - if (pollingJob == null || pollingJob.isCancelled()) { + ScheduledFuture job = pollingJob; + if (job == null || job.isCancelled()) { pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, 0, config.refreshInterval, TimeUnit.MINUTES); } } @Override public void dispose() { - if (pollingJob != null) { - pollingJob.cancel(true); + ScheduledFuture job = pollingJob; + if (job != null) { + job.cancel(true); pollingJob = null; } super.dispose(); @@ -127,21 +130,27 @@ private void getStatus() { } private void updateState(Car car) { - if (car.batteryLevel != null) { - updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(car.batteryLevel)); + Double batteryLevel = car.batteryLevel; + if (batteryLevel != null) { + updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); } - if (car.hvacstatus != null) { - updateState(CHANNEL_HVAC_STATUS, OnOffType.from(car.hvacstatus)); + Boolean hvacstatus = car.hvacstatus; + if (hvacstatus != null) { + updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); } - if (car.imageURL != null && !car.imageURL.isEmpty()) { - updateState(CHANNEL_IMAGE, new StringType(car.imageURL)); + String imageURL = car.imageURL; + if (imageURL != null && !imageURL.isEmpty()) { + updateState(CHANNEL_IMAGE, new StringType(imageURL)); } - if (car.gpsLatitude != null && car.gpsLongitude != null) { + Double latitude = car.gpsLatitude; + Double longitude = car.gpsLongitude; + if (latitude != null && longitude != null) { updateState(CHANNEL_LOCATION, - new PointType(new DecimalType(car.gpsLatitude), new DecimalType(car.gpsLongitude))); + new PointType(new DecimalType(latitude.doubleValue()), new DecimalType(longitude.doubleValue()))); } - if (car.odometer != null) { - updateState(CHANNEL_ODOMETER, new QuantityType(Double.valueOf(car.odometer), KILO(METRE))); + Double odometer = car.odometer; + if (odometer != null) { + updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); } } } From 852cd6b25808a3fe127754edbf1faf8570571dc5 Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Fri, 3 Dec 2021 18:48:22 +0100 Subject: [PATCH 09/13] [renault] Persist car in handler. Only get the car image url once. New Forbbidden Error case when car not paired.. Signed-off-by: Culnane Douglas --- .../internal/api/MyRenaultHttpSession.java | 38 +++++++++---------- .../exceptions/RenaultForbiddenException.java | 30 +++++++++++++++ .../internal/handler/RenaultHandler.java | 18 ++++++--- 3 files changed, 62 insertions(+), 24 deletions(-) create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java index 00955767892d..513c57231abd 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.util.Fields; import org.openhab.binding.renault.internal.RenaultConfiguration; import org.openhab.binding.renault.internal.api.exceptions.RenaultException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +44,6 @@ @NonNullByDefault public class MyRenaultHttpSession { - private Car car = new Car(); private RenaultConfiguration config; private HttpClient httpClient; private Constants constants; @@ -62,19 +62,20 @@ public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) this.constants = new Constants(config.locale); } - public void initSesssion() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { + public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, InterruptedException, + ExecutionException, TimeoutException { login(); getAccountInfo(); getJWT(); getAccountID(); - getVehicle(); + getVehicle(car); } - public void updateCarData() { - getCockpit(); - getBatteryStatus(); - getLocation(); - getHvacStatus(); + public void updateCarData(Car car) throws RenaultForbiddenException { + getCockpit(car); + getBatteryStatus(car); + getLocation(car); + getHvacStatus(car); } private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { @@ -161,7 +162,7 @@ private void getJWT() throws RenaultException, InterruptedException, ExecutionEx } } - private void getAccountID() throws RenaultException { + private void getAccountID() throws RenaultException, RenaultForbiddenException { JsonObject responseJson = getKamereonResponse( "/commerce/v1/persons/" + personId + "?country=" + getCountry(config)); if (responseJson != null) { @@ -178,7 +179,7 @@ private void getAccountID() throws RenaultException { } } - private void getVehicle() { + private void getVehicle(Car car) throws RenaultForbiddenException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/" + config.vin + "/details?country=" + getCountry(config)); if (responseJson != null) { @@ -186,7 +187,7 @@ private void getVehicle() { } } - private void getBatteryStatus() { + private void getBatteryStatus(Car car) throws RenaultForbiddenException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/battery-status?country=" + getCountry(config)); if (responseJson != null) { @@ -194,7 +195,7 @@ private void getBatteryStatus() { } } - private void getHvacStatus() { + private void getHvacStatus(Car car) throws RenaultForbiddenException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/hvac-status?country=" + getCountry(config)); if (responseJson != null) { @@ -202,7 +203,7 @@ private void getHvacStatus() { } } - private void getCockpit() { + private void getCockpit(Car car) throws RenaultForbiddenException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/cockpit?country=" + getCountry(config)); if (responseJson != null) { @@ -210,7 +211,7 @@ private void getCockpit() { } } - private void getLocation() { + private void getLocation(Car car) throws RenaultForbiddenException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/location?country=" + getCountry(config)); if (responseJson != null) { @@ -218,7 +219,7 @@ private void getLocation() { } } - private @Nullable JsonObject getKamereonResponse(String path) { + private @Nullable JsonObject getKamereonResponse(String path) throws RenaultForbiddenException { Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET) .header("Content-type", "application/vnd.api+json").header("apikey", this.constants.getKamereonApiKey()) .header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt); @@ -227,6 +228,9 @@ private void getLocation() { if (HttpStatus.OK_200 == response.getStatus()) { logger.debug("Kamereon Response: {}", response.getContentAsString()); return JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); + } else if (HttpStatus.FORBIDDEN_403 == response.getStatus()) { + throw new RenaultForbiddenException( + "Kamereon Response Forbidden! Ensure the car is paired in your MyRenault App."); } else { logger.warn("Kamereon Response: [{}] {} {}", response.getStatus(), response.getReason(), response.getContentAsString()); @@ -244,8 +248,4 @@ private String getCountry(RenaultConfiguration config) { } return country; } - - public Car getCar() { - return car; - } } diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java new file mode 100644 index 000000000000..3a737ea59690 --- /dev/null +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java @@ -0,0 +1,30 @@ +/** + * 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.api.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception thrown while trying to access the My Renault web services. + * + * @author Doug Culnane - Initial contribution + */ +@NonNullByDefault +public class RenaultForbiddenException extends Exception { + + private static final long serialVersionUID = 1L; + + public RenaultForbiddenException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java index b849d1e7b13e..9a46934c6bfe 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java @@ -27,6 +27,7 @@ import org.openhab.binding.renault.internal.RenaultConfiguration; import org.openhab.binding.renault.internal.api.Car; import org.openhab.binding.renault.internal.api.MyRenaultHttpSession; +import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PointType; @@ -58,8 +59,11 @@ public class RenaultHandler extends BaseThingHandler { private HttpClient httpClient; + private Car car; + public RenaultHandler(Thing thing, HttpClient httpClient) { super(thing); + this.car = new Car(); this.httpClient = httpClient; } @@ -116,20 +120,24 @@ public void dispose() { private void getStatus() { MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient); try { - httpSession.initSesssion(); + httpSession.initSesssion(car); updateStatus(ThingStatus.ONLINE); } catch (Exception e) { httpSession = null; logger.warn("Error My Renault Http Session.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - if (httpSession != null) { - httpSession.updateCarData(); - updateState(httpSession.getCar()); + try { + if (httpSession != null) { + httpSession.updateCarData(car); + updateState(); + } + } catch (RenaultForbiddenException e) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } - private void updateState(Car car) { + private void updateState() { Double batteryLevel = car.batteryLevel; if (batteryLevel != null) { updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); From 70d765a200513982b87bc5879b9331f153f0b947 Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Fri, 3 Dec 2021 19:58:17 +0100 Subject: [PATCH 10/13] [renault] More transparent Error on update. Signed-off-by: Culnane Douglas --- .../internal/api/MyRenaultHttpSession.java | 42 +++++----- .../exceptions/RenaultForbiddenException.java | 3 +- .../exceptions/RenaultUpdateException.java | 30 +++++++ .../internal/handler/RenaultHandler.java | 82 +++++++++++++------ 4 files changed, 112 insertions(+), 45 deletions(-) create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultUpdateException.java diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java index 513c57231abd..d9140f480685 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java @@ -26,6 +26,7 @@ import org.openhab.binding.renault.internal.RenaultConfiguration; import org.openhab.binding.renault.internal.api.exceptions.RenaultException; import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,20 +63,17 @@ public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) this.constants = new Constants(config.locale); } - public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, InterruptedException, - ExecutionException, TimeoutException { + public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, RenaultUpdateException, + InterruptedException, ExecutionException, TimeoutException { login(); getAccountInfo(); getJWT(); getAccountID(); - getVehicle(car); - } - public void updateCarData(Car car) throws RenaultForbiddenException { - getCockpit(car); - getBatteryStatus(car); - getLocation(car); - getHvacStatus(car); + final String imageURL = car.imageURL; + if (imageURL == null) { + getVehicle(car); + } } private void login() throws RenaultException, InterruptedException, ExecutionException, TimeoutException { @@ -162,7 +160,7 @@ private void getJWT() throws RenaultException, InterruptedException, ExecutionEx } } - private void getAccountID() throws RenaultException, RenaultForbiddenException { + private void getAccountID() throws RenaultException, RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse( "/commerce/v1/persons/" + personId + "?country=" + getCountry(config)); if (responseJson != null) { @@ -179,7 +177,7 @@ private void getAccountID() throws RenaultException, RenaultForbiddenException { } } - private void getVehicle(Car car) throws RenaultForbiddenException { + public void getVehicle(Car car) throws RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/" + config.vin + "/details?country=" + getCountry(config)); if (responseJson != null) { @@ -187,7 +185,7 @@ private void getVehicle(Car car) throws RenaultForbiddenException { } } - private void getBatteryStatus(Car car) throws RenaultForbiddenException { + public void getBatteryStatus(Car car) throws RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/battery-status?country=" + getCountry(config)); if (responseJson != null) { @@ -195,7 +193,7 @@ private void getBatteryStatus(Car car) throws RenaultForbiddenException { } } - private void getHvacStatus(Car car) throws RenaultForbiddenException { + public void getHvacStatus(Car car) throws RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/hvac-status?country=" + getCountry(config)); if (responseJson != null) { @@ -203,7 +201,7 @@ private void getHvacStatus(Car car) throws RenaultForbiddenException { } } - private void getCockpit(Car car) throws RenaultForbiddenException { + public void getCockpit(Car car) throws RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/cockpit?country=" + getCountry(config)); if (responseJson != null) { @@ -211,7 +209,7 @@ private void getCockpit(Car car) throws RenaultForbiddenException { } } - private void getLocation(Car car) throws RenaultForbiddenException { + public void getLocation(Car car) throws RenaultForbiddenException, RenaultUpdateException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/location?country=" + getCountry(config)); if (responseJson != null) { @@ -219,7 +217,8 @@ private void getLocation(Car car) throws RenaultForbiddenException { } } - private @Nullable JsonObject getKamereonResponse(String path) throws RenaultForbiddenException { + private @Nullable JsonObject getKamereonResponse(String path) + throws RenaultForbiddenException, RenaultUpdateException { Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET) .header("Content-type", "application/vnd.api+json").header("apikey", this.constants.getKamereonApiKey()) .header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt); @@ -228,12 +227,17 @@ private void getLocation(Car car) throws RenaultForbiddenException { if (HttpStatus.OK_200 == response.getStatus()) { logger.debug("Kamereon Response: {}", response.getContentAsString()); return JsonParser.parseString(response.getContentAsString()).getAsJsonObject(); - } else if (HttpStatus.FORBIDDEN_403 == response.getStatus()) { - throw new RenaultForbiddenException( - "Kamereon Response Forbidden! Ensure the car is paired in your MyRenault App."); } else { logger.warn("Kamereon Response: [{}] {} {}", response.getStatus(), response.getReason(), response.getContentAsString()); + + if (HttpStatus.FORBIDDEN_403 == response.getStatus()) { + throw new RenaultForbiddenException( + "Kamereon Response Forbidden! Ensure the car is paired in your MyRenault App."); + } else { + throw new RenaultUpdateException( + "Kamereon Response Failed! Error: [" + response.getStatus() + "] " + response.getReason()); + } } } catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) { logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage()); diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java index 3a737ea59690..f469daf6aec8 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultForbiddenException.java @@ -15,7 +15,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * Exception thrown while trying to access the My Renault web services. + * Exception thrown while trying to access the My Renault web services when HTTP + * 403 is returned. Normally means the car is not paired to the account. * * @author Doug Culnane - Initial contribution */ diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultUpdateException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultUpdateException.java new file mode 100644 index 000000000000..a7266f730427 --- /dev/null +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultUpdateException.java @@ -0,0 +1,30 @@ +/** + * 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.api.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception thrown while trying to update the My Renault car information. + * + * @author Doug Culnane - Initial contribution + */ +@NonNullByDefault +public class RenaultUpdateException extends Exception { + + private static final long serialVersionUID = 1L; + + public RenaultUpdateException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java index 9a46934c6bfe..1dd2b0f1e51d 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.renault.internal.api.Car; import org.openhab.binding.renault.internal.api.MyRenaultHttpSession; import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PointType; @@ -127,38 +128,69 @@ private void getStatus() { logger.warn("Error My Renault Http Session.", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - try { - if (httpSession != null) { - httpSession.updateCarData(car); - updateState(); + if (httpSession != null) { + String imageURL = car.imageURL; + if (imageURL != null && !imageURL.isEmpty()) { + updateState(CHANNEL_IMAGE, new StringType(imageURL)); } - } catch (RenaultForbiddenException e) { - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + updateHvacStatus(httpSession); + updateCockpit(httpSession); + updateLocation(httpSession); + updateBattery(httpSession); } } - private void updateState() { - Double batteryLevel = car.batteryLevel; - if (batteryLevel != null) { - updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); - } - Boolean hvacstatus = car.hvacstatus; - if (hvacstatus != null) { - updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); + private void updateHvacStatus(MyRenaultHttpSession httpSession) { + try { + httpSession.getHvacStatus(car); + Boolean hvacstatus = car.hvacstatus; + if (hvacstatus != null) { + updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); + } + } catch (RenaultForbiddenException | RenaultUpdateException e) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Update HVAC error: " + e.getMessage()); } - String imageURL = car.imageURL; - if (imageURL != null && !imageURL.isEmpty()) { - updateState(CHANNEL_IMAGE, new StringType(imageURL)); + } + + private void updateLocation(MyRenaultHttpSession httpSession) { + try { + httpSession.getLocation(car); + Double latitude = car.gpsLatitude; + Double longitude = car.gpsLongitude; + if (latitude != null && longitude != null) { + updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()), + new DecimalType(longitude.doubleValue()))); + } + } catch (RenaultForbiddenException | RenaultUpdateException e) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Update location error: " + e.getMessage()); } - Double latitude = car.gpsLatitude; - Double longitude = car.gpsLongitude; - if (latitude != null && longitude != null) { - updateState(CHANNEL_LOCATION, - new PointType(new DecimalType(latitude.doubleValue()), new DecimalType(longitude.doubleValue()))); + } + + private void updateCockpit(MyRenaultHttpSession httpSession) { + try { + httpSession.getCockpit(car); + Double odometer = car.odometer; + if (odometer != null) { + updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); + } + } catch (RenaultForbiddenException | RenaultUpdateException e) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Update cockpit error: " + e.getMessage()); } - Double odometer = car.odometer; - if (odometer != null) { - updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); + } + + private void updateBattery(MyRenaultHttpSession httpSession) { + try { + httpSession.getBatteryStatus(car); + Double batteryLevel = car.batteryLevel; + if (batteryLevel != null) { + updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); + } + } catch (RenaultForbiddenException | RenaultUpdateException e) { + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Update battery error: " + e.getMessage()); } } } From f249eaeea5764bc1119b0752b48c5d9b4ca25ee2 Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Sat, 4 Dec 2021 19:58:55 +0100 Subject: [PATCH 11/13] [renault] New NotImplemented Exception and keep errors to the log. Signed-off-by: Culnane Douglas --- .../binding/renault/internal/api/Car.java | 5 ++ .../internal/api/MyRenaultHttpSession.java | 27 ++++--- .../internal/handler/RenaultHandler.java | 80 +++++++++++-------- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java index 8819942bf046..cf57e54eec67 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java @@ -32,6 +32,11 @@ public class Car { private final Logger logger = LoggerFactory.getLogger(Car.class); + public boolean disableLocation = false; + public boolean disableBattery = false; + public boolean disableCockpit = false; + public boolean disableHvac = false; + public @Nullable Double batteryLevel; public @Nullable Boolean hvacstatus; public @Nullable Double odometer; diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java index d9140f480685..656a24cf1853 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java @@ -26,6 +26,7 @@ import org.openhab.binding.renault.internal.RenaultConfiguration; import org.openhab.binding.renault.internal.api.exceptions.RenaultException; import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultNotImplementedException; import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,7 +65,7 @@ public MyRenaultHttpSession(RenaultConfiguration config, HttpClient httpClient) } public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, RenaultUpdateException, - InterruptedException, ExecutionException, TimeoutException { + RenaultNotImplementedException, InterruptedException, ExecutionException, TimeoutException { login(); getAccountInfo(); getJWT(); @@ -160,7 +161,8 @@ private void getJWT() throws RenaultException, InterruptedException, ExecutionEx } } - private void getAccountID() throws RenaultException, RenaultForbiddenException, RenaultUpdateException { + private void getAccountID() + throws RenaultException, RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse( "/commerce/v1/persons/" + personId + "?country=" + getCountry(config)); if (responseJson != null) { @@ -177,7 +179,8 @@ private void getAccountID() throws RenaultException, RenaultForbiddenException, } } - public void getVehicle(Car car) throws RenaultForbiddenException, RenaultUpdateException { + public void getVehicle(Car car) + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/vehicles/" + config.vin + "/details?country=" + getCountry(config)); if (responseJson != null) { @@ -185,7 +188,8 @@ public void getVehicle(Car car) throws RenaultForbiddenException, RenaultUpdateE } } - public void getBatteryStatus(Car car) throws RenaultForbiddenException, RenaultUpdateException { + public void getBatteryStatus(Car car) + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/battery-status?country=" + getCountry(config)); if (responseJson != null) { @@ -193,7 +197,8 @@ public void getBatteryStatus(Car car) throws RenaultForbiddenException, RenaultU } } - public void getHvacStatus(Car car) throws RenaultForbiddenException, RenaultUpdateException { + public void getHvacStatus(Car car) + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/hvac-status?country=" + getCountry(config)); if (responseJson != null) { @@ -201,7 +206,8 @@ public void getHvacStatus(Car car) throws RenaultForbiddenException, RenaultUpda } } - public void getCockpit(Car car) throws RenaultForbiddenException, RenaultUpdateException { + public void getCockpit(Car car) + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v2/cars/" + config.vin + "/cockpit?country=" + getCountry(config)); if (responseJson != null) { @@ -209,7 +215,8 @@ public void getCockpit(Car car) throws RenaultForbiddenException, RenaultUpdateE } } - public void getLocation(Car car) throws RenaultForbiddenException, RenaultUpdateException { + public void getLocation(Car car) + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { JsonObject responseJson = getKamereonResponse("/commerce/v1/accounts/" + kamereonaccountId + "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/location?country=" + getCountry(config)); if (responseJson != null) { @@ -218,7 +225,7 @@ public void getLocation(Car car) throws RenaultForbiddenException, RenaultUpdate } private @Nullable JsonObject getKamereonResponse(String path) - throws RenaultForbiddenException, RenaultUpdateException { + throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException { Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET) .header("Content-type", "application/vnd.api+json").header("apikey", this.constants.getKamereonApiKey()) .header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt); @@ -230,10 +237,12 @@ public void getLocation(Car car) throws RenaultForbiddenException, RenaultUpdate } else { logger.warn("Kamereon Response: [{}] {} {}", response.getStatus(), response.getReason(), response.getContentAsString()); - if (HttpStatus.FORBIDDEN_403 == response.getStatus()) { throw new RenaultForbiddenException( "Kamereon Response Forbidden! Ensure the car is paired in your MyRenault App."); + } else if (HttpStatus.NOT_IMPLEMENTED_501 == response.getStatus()) { + throw new RenaultNotImplementedException( + "Kamereon Service Not Implemented: [" + response.getStatus() + "] " + response.getReason()); } else { throw new RenaultUpdateException( "Kamereon Response Failed! Error: [" + response.getStatus() + "] " + response.getReason()); diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java index 1dd2b0f1e51d..17401a8c3e19 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.renault.internal.api.Car; import org.openhab.binding.renault.internal.api.MyRenaultHttpSession; import org.openhab.binding.renault.internal.api.exceptions.RenaultForbiddenException; +import org.openhab.binding.renault.internal.api.exceptions.RenaultNotImplementedException; import org.openhab.binding.renault.internal.api.exceptions.RenaultUpdateException; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -75,6 +76,9 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void initialize() { + + // reset the car on initialize + this.car = new Car(); this.config = getConfigAs(RenaultConfiguration.class); // Validate configuration @@ -141,56 +145,64 @@ private void getStatus() { } private void updateHvacStatus(MyRenaultHttpSession httpSession) { - try { - httpSession.getHvacStatus(car); - Boolean hvacstatus = car.hvacstatus; - if (hvacstatus != null) { - updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); + if (!car.disableHvac) { + try { + httpSession.getHvacStatus(car); + Boolean hvacstatus = car.hvacstatus; + if (hvacstatus != null) { + updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); + } + } catch (RenaultNotImplementedException e) { + car.disableHvac = true; + } catch (RenaultForbiddenException | RenaultUpdateException e) { } - } catch (RenaultForbiddenException | RenaultUpdateException e) { - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Update HVAC error: " + e.getMessage()); } } private void updateLocation(MyRenaultHttpSession httpSession) { - try { - httpSession.getLocation(car); - Double latitude = car.gpsLatitude; - Double longitude = car.gpsLongitude; - if (latitude != null && longitude != null) { - updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()), - new DecimalType(longitude.doubleValue()))); + if (!car.disableLocation) { + try { + httpSession.getLocation(car); + Double latitude = car.gpsLatitude; + Double longitude = car.gpsLongitude; + if (latitude != null && longitude != null) { + updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()), + new DecimalType(longitude.doubleValue()))); + } + } catch (RenaultNotImplementedException e) { + car.disableLocation = true; + } catch (RenaultForbiddenException | RenaultUpdateException e) { } - } catch (RenaultForbiddenException | RenaultUpdateException e) { - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Update location error: " + e.getMessage()); } } private void updateCockpit(MyRenaultHttpSession httpSession) { - try { - httpSession.getCockpit(car); - Double odometer = car.odometer; - if (odometer != null) { - updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); + if (!car.disableCockpit) { + try { + httpSession.getCockpit(car); + Double odometer = car.odometer; + if (odometer != null) { + updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); + } + } catch (RenaultNotImplementedException e) { + car.disableCockpit = true; + } catch (RenaultForbiddenException | RenaultUpdateException e) { } - } catch (RenaultForbiddenException | RenaultUpdateException e) { - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Update cockpit error: " + e.getMessage()); } } private void updateBattery(MyRenaultHttpSession httpSession) { - try { - httpSession.getBatteryStatus(car); - Double batteryLevel = car.batteryLevel; - if (batteryLevel != null) { - updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); + if (!car.disableBattery) { + try { + httpSession.getBatteryStatus(car); + Double batteryLevel = car.batteryLevel; + if (batteryLevel != null) { + updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); + } + } catch (RenaultNotImplementedException e) { + car.disableBattery = true; + } catch (RenaultForbiddenException | RenaultUpdateException e) { } - } catch (RenaultForbiddenException | RenaultUpdateException e) { - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Update battery error: " + e.getMessage()); } } } From adb5b9572145787a58d5b64ce7c3634537e45deb Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Sat, 4 Dec 2021 20:02:11 +0100 Subject: [PATCH 12/13] [renault] Add the new NotImplemented Exception... Signed-off-by: Culnane Douglas --- .../RenaultNotImplementedException.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultNotImplementedException.java diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultNotImplementedException.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultNotImplementedException.java new file mode 100644 index 000000000000..d948cbc7a44a --- /dev/null +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/exceptions/RenaultNotImplementedException.java @@ -0,0 +1,31 @@ +/** + * 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.api.exceptions; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception thrown while trying to access the My Renault service for information + * that is not implemented. + * + * @author Doug Culnane - Initial contribution + */ +@NonNullByDefault +public class RenaultNotImplementedException extends Exception { + + private static final long serialVersionUID = 1L; + + public RenaultNotImplementedException(String message) { + super(message); + } +} From 5751dfc2e89891fd72ffe5db9217a0c0fff26740 Mon Sep 17 00:00:00 2001 From: Culnane Douglas Date: Sun, 5 Dec 2021 09:13:34 +0100 Subject: [PATCH 13/13] [renault] Getters and Setters for Car. Signed-off-by: Culnane Douglas --- .../binding/renault/internal/api/Car.java | 102 ++++++++++++++++-- .../internal/api/MyRenaultHttpSession.java | 2 +- .../internal/handler/RenaultHandler.java | 29 +++-- 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java index cf57e54eec67..fec7c182f0ae 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/Car.java @@ -32,17 +32,17 @@ public class Car { private final Logger logger = LoggerFactory.getLogger(Car.class); - public boolean disableLocation = false; - public boolean disableBattery = false; - public boolean disableCockpit = false; - public boolean disableHvac = false; - - public @Nullable Double batteryLevel; - public @Nullable Boolean hvacstatus; - public @Nullable Double odometer; - public @Nullable String imageURL; - public @Nullable Double gpsLatitude; - public @Nullable Double gpsLongitude; + private boolean disableLocation = false; + private boolean disableBattery = false; + private boolean disableCockpit = false; + private boolean disableHvac = false; + + private @Nullable Double batteryLevel; + private @Nullable Boolean hvacstatus; + private @Nullable Double odometer; + private @Nullable String imageURL; + private @Nullable Double gpsLatitude; + private @Nullable Double gpsLongitude; public void setBatteryStatus(JsonObject responseJson) { try { @@ -124,6 +124,86 @@ public void setDetails(JsonObject responseJson) { } } + public boolean isDisableLocation() { + return disableLocation; + } + + public void setDisableLocation(boolean disableLocation) { + this.disableLocation = disableLocation; + } + + public boolean isDisableBattery() { + return disableBattery; + } + + public void setDisableBattery(boolean disableBattery) { + this.disableBattery = disableBattery; + } + + public boolean isDisableCockpit() { + return disableCockpit; + } + + public void setDisableCockpit(boolean disableCockpit) { + this.disableCockpit = disableCockpit; + } + + public boolean isDisableHvac() { + return disableHvac; + } + + public void setDisableHvac(boolean disableHvac) { + this.disableHvac = disableHvac; + } + + public @Nullable Double getBatteryLevel() { + return batteryLevel; + } + + public void setBatteryLevel(Double batteryLevel) { + this.batteryLevel = batteryLevel; + } + + public @Nullable Boolean getHvacstatus() { + return hvacstatus; + } + + public void setHvacstatus(Boolean hvacstatus) { + this.hvacstatus = hvacstatus; + } + + public @Nullable Double getOdometer() { + return odometer; + } + + public void setOdometer(Double odometer) { + this.odometer = odometer; + } + + public @Nullable String getImageURL() { + return imageURL; + } + + public void setImageURL(String imageURL) { + this.imageURL = imageURL; + } + + public @Nullable Double getGpsLatitude() { + return gpsLatitude; + } + + public void setGpsLatitude(Double gpsLatitude) { + this.gpsLatitude = gpsLatitude; + } + + public @Nullable Double getGpsLongitude() { + return gpsLongitude; + } + + public void setGpsLongitude(Double gpsLongitude) { + this.gpsLongitude = gpsLongitude; + } + private @Nullable JsonObject getAttributes(JsonObject responseJson) throws IllegalStateException, ClassCastException { if (responseJson.get("data") != null && responseJson.get("data").getAsJsonObject().get("attributes") != null) { diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java index 656a24cf1853..b7ca956e2fef 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/api/MyRenaultHttpSession.java @@ -71,7 +71,7 @@ public void initSesssion(Car car) throws RenaultException, RenaultForbiddenExcep getJWT(); getAccountID(); - final String imageURL = car.imageURL; + final String imageURL = car.getImageURL(); if (imageURL == null) { getVehicle(car); } diff --git a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java index 17401a8c3e19..fedfab292bae 100644 --- a/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java +++ b/bundles/org.openhab.binding.renault/src/main/java/org/openhab/binding/renault/internal/handler/RenaultHandler.java @@ -76,7 +76,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void initialize() { - // reset the car on initialize this.car = new Car(); this.config = getConfigAs(RenaultConfiguration.class); @@ -133,7 +132,7 @@ private void getStatus() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } if (httpSession != null) { - String imageURL = car.imageURL; + String imageURL = car.getImageURL(); if (imageURL != null && !imageURL.isEmpty()) { updateState(CHANNEL_IMAGE, new StringType(imageURL)); } @@ -145,62 +144,62 @@ private void getStatus() { } private void updateHvacStatus(MyRenaultHttpSession httpSession) { - if (!car.disableHvac) { + if (!car.isDisableHvac()) { try { httpSession.getHvacStatus(car); - Boolean hvacstatus = car.hvacstatus; + Boolean hvacstatus = car.getHvacstatus(); if (hvacstatus != null) { updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue())); } } catch (RenaultNotImplementedException e) { - car.disableHvac = true; + car.setDisableHvac(true); } catch (RenaultForbiddenException | RenaultUpdateException e) { } } } private void updateLocation(MyRenaultHttpSession httpSession) { - if (!car.disableLocation) { + if (!car.isDisableLocation()) { try { httpSession.getLocation(car); - Double latitude = car.gpsLatitude; - Double longitude = car.gpsLongitude; + Double latitude = car.getGpsLatitude(); + Double longitude = car.getGpsLongitude(); if (latitude != null && longitude != null) { updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()), new DecimalType(longitude.doubleValue()))); } } catch (RenaultNotImplementedException e) { - car.disableLocation = true; + car.setDisableLocation(true); } catch (RenaultForbiddenException | RenaultUpdateException e) { } } } private void updateCockpit(MyRenaultHttpSession httpSession) { - if (!car.disableCockpit) { + if (!car.isDisableCockpit()) { try { httpSession.getCockpit(car); - Double odometer = car.odometer; + Double odometer = car.getOdometer(); if (odometer != null) { updateState(CHANNEL_ODOMETER, new QuantityType(odometer.doubleValue(), KILO(METRE))); } } catch (RenaultNotImplementedException e) { - car.disableCockpit = true; + car.setDisableCockpit(true); } catch (RenaultForbiddenException | RenaultUpdateException e) { } } } private void updateBattery(MyRenaultHttpSession httpSession) { - if (!car.disableBattery) { + if (!car.isDisableBattery()) { try { httpSession.getBatteryStatus(car); - Double batteryLevel = car.batteryLevel; + Double batteryLevel = car.getBatteryLevel(); if (batteryLevel != null) { updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue())); } } catch (RenaultNotImplementedException e) { - car.disableBattery = true; + car.setDisableBattery(true); } catch (RenaultForbiddenException | RenaultUpdateException e) { } }