mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[renault] Initial Contribution (#11467)
* #11465 Initial renault-api binding Signed-off-by: Doug Culnane <doug@culnane.com>
This commit is contained in:
parent
9340213eb0
commit
3d2663cbf7
@ -253,6 +253,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
|
||||
|
@ -1251,6 +1251,11 @@
|
||||
<artifactId>org.openhab.binding.remoteopenhab</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.renault</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.resol</artifactId>
|
||||
|
13
bundles/org.openhab.binding.renault/NOTICE
Normal file
13
bundles/org.openhab.binding.renault/NOTICE
Normal file
@ -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
|
43
bundles/org.openhab.binding.renault/README.md
Normal file
43
bundles/org.openhab.binding.renault/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
You require your MyRenault credential, locale and VIN for your MyRenault registered car.
|
||||
|
||||
| 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. | 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:Length | Total distance travelled |
|
||||
|
||||
|
17
bundles/org.openhab.binding.renault/pom.xml
Normal file
17
bundles/org.openhab.binding.renault/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.renault</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Renault Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.renault-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-renault" description="Renault Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.renault/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -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";
|
||||
}
|
@ -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;
|
||||
|
||||
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 = 10;
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* 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.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;
|
||||
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;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* 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<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_CAR);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public RenaultHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@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, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* MyRenault registered car for parsing HTTP responses and collecting data and
|
||||
* information.
|
||||
*
|
||||
* @author Doug Culnane - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Car {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(Car.class);
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 Location: {}", e.getMessage(), responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
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")) {
|
||||
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")) {
|
||||
url = rendition.getAsJsonObject().get("url").getAsString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (url != null && !url.isEmpty()) {
|
||||
imageURL = url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException | ClassCastException e) {
|
||||
logger.warn("Error {} parsing Details: {}", e.getMessage(), 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) {
|
||||
return responseJson.get("data").getAsJsonObject().get("attributes").getAsJsonObject();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Constants for Renault API.
|
||||
*
|
||||
* https://github.com/hacf-fr/renault-api/blob/main/src/renault_api/const.py
|
||||
*
|
||||
* @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";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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.HttpMethod;
|
||||
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.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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 RenaultConfiguration config;
|
||||
private HttpClient httpClient;
|
||||
private 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, HttpClient httpClient) {
|
||||
this.config = config;
|
||||
this.httpClient = httpClient;
|
||||
this.constants = new Constants(config.locale);
|
||||
}
|
||||
|
||||
public void initSesssion(Car car) throws RenaultException, RenaultForbiddenException, RenaultUpdateException,
|
||||
RenaultNotImplementedException, InterruptedException, ExecutionException, TimeoutException {
|
||||
login();
|
||||
getAccountInfo();
|
||||
getJWT();
|
||||
getAccountID();
|
||||
|
||||
final String imageURL = car.getImageURL();
|
||||
if (imageURL == null) {
|
||||
getVehicle(car);
|
||||
}
|
||||
}
|
||||
|
||||
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 = httpClient.FORM(this.constants.getGigyaRootUrl() + "/accounts.login", fields);
|
||||
if (HttpStatus.OK_200 == response.getStatus()) {
|
||||
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());
|
||||
throw new RenaultException("Login Error: " + response.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
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());
|
||||
throw new RenaultException("Get Account Info Error: " + response.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
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());
|
||||
throw new RenaultException("Get JWT Error: " + response.getReason());
|
||||
}
|
||||
}
|
||||
|
||||
private void getAccountID()
|
||||
throws RenaultException, RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kamereonaccountId == null) {
|
||||
throw new RenaultException("Can not get Kamereon MyRenault Account ID!");
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
car.setDetails(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
car.setBatteryStatus(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
car.setHVACStatus(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
car.setCockpit(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
car.setLocation(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable JsonObject getKamereonResponse(String path)
|
||||
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);
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getCountry(RenaultConfiguration config) {
|
||||
String country = "XX";
|
||||
if (config.locale.length() == 5) {
|
||||
country = config.locale.substring(3);
|
||||
}
|
||||
return country;
|
||||
}
|
||||
}
|
@ -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 RenaultException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RenaultException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -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 web services when HTTP
|
||||
* 403 is returned. Normally means the car is not paired to the account.
|
||||
*
|
||||
* @author Doug Culnane - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class RenaultForbiddenException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RenaultForbiddenException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 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.handler;
|
||||
|
||||
import static org.openhab.binding.renault.internal.RenaultBindingConstants.*;
|
||||
import static org.openhab.core.library.unit.MetricPrefix.KILO;
|
||||
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.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.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;
|
||||
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;
|
||||
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 RenaultConfiguration config = new RenaultConfiguration();
|
||||
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
private Car car;
|
||||
|
||||
public RenaultHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.car = new Car();
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// This binding only polls status data automatically.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// reset the car on initialize
|
||||
this.car = new Car();
|
||||
this.config = getConfigAs(RenaultConfiguration.class);
|
||||
|
||||
// Validate configuration
|
||||
if (this.config.myRenaultUsername.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Username is empty!");
|
||||
return;
|
||||
}
|
||||
if (this.config.myRenaultPassword.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "MyRenault Password is empty!");
|
||||
return;
|
||||
}
|
||||
if (this.config.locale.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Location is empty!");
|
||||
return;
|
||||
}
|
||||
if (this.config.vin.isBlank()) {
|
||||
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;
|
||||
}
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// Background initialization:
|
||||
ScheduledFuture<?> job = pollingJob;
|
||||
if (job == null || job.isCancelled()) {
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, 0, config.refreshInterval, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
ScheduledFuture<?> job = pollingJob;
|
||||
if (job != null) {
|
||||
job.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void getStatus() {
|
||||
MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
|
||||
try {
|
||||
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) {
|
||||
String imageURL = car.getImageURL();
|
||||
if (imageURL != null && !imageURL.isEmpty()) {
|
||||
updateState(CHANNEL_IMAGE, new StringType(imageURL));
|
||||
}
|
||||
updateHvacStatus(httpSession);
|
||||
updateCockpit(httpSession);
|
||||
updateLocation(httpSession);
|
||||
updateBattery(httpSession);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateHvacStatus(MyRenaultHttpSession httpSession) {
|
||||
if (!car.isDisableHvac()) {
|
||||
try {
|
||||
httpSession.getHvacStatus(car);
|
||||
Boolean hvacstatus = car.getHvacstatus();
|
||||
if (hvacstatus != null) {
|
||||
updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue()));
|
||||
}
|
||||
} catch (RenaultNotImplementedException e) {
|
||||
car.setDisableHvac(true);
|
||||
} catch (RenaultForbiddenException | RenaultUpdateException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLocation(MyRenaultHttpSession httpSession) {
|
||||
if (!car.isDisableLocation()) {
|
||||
try {
|
||||
httpSession.getLocation(car);
|
||||
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.setDisableLocation(true);
|
||||
} catch (RenaultForbiddenException | RenaultUpdateException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCockpit(MyRenaultHttpSession httpSession) {
|
||||
if (!car.isDisableCockpit()) {
|
||||
try {
|
||||
httpSession.getCockpit(car);
|
||||
Double odometer = car.getOdometer();
|
||||
if (odometer != null) {
|
||||
updateState(CHANNEL_ODOMETER, new QuantityType<Length>(odometer.doubleValue(), KILO(METRE)));
|
||||
}
|
||||
} catch (RenaultNotImplementedException e) {
|
||||
car.setDisableCockpit(true);
|
||||
} catch (RenaultForbiddenException | RenaultUpdateException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBattery(MyRenaultHttpSession httpSession) {
|
||||
if (!car.isDisableBattery()) {
|
||||
try {
|
||||
httpSession.getBatteryStatus(car);
|
||||
Double batteryLevel = car.getBatteryLevel();
|
||||
if (batteryLevel != null) {
|
||||
updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue()));
|
||||
}
|
||||
} catch (RenaultNotImplementedException e) {
|
||||
car.setDisableBattery(true);
|
||||
} catch (RenaultForbiddenException | RenaultUpdateException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="renault" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Renault Binding</name>
|
||||
<description>This is the binding for Renault electric cars.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="renault"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
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">
|
||||
|
||||
<thing-type id="car">
|
||||
|
||||
<label>Renault Car</label>
|
||||
<description>A MyRenault registered car.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="batterylevel" typeId="system.battery-level"/>
|
||||
<channel id="hvacstatus" typeId="hvacstatus"/>
|
||||
<channel id="image" typeId="image"/>
|
||||
<channel id="location" typeId="system.location"/>
|
||||
<channel id="odometer" typeId="odometer"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
|
||||
<parameter name="myRenaultUsername" type="text" required="true">
|
||||
<label>MyRenault Username</label>
|
||||
</parameter>
|
||||
<parameter name="myRenaultPassword" type="text" required="true">
|
||||
<context>password</context>
|
||||
<label>MyRenault Password</label>
|
||||
</parameter>
|
||||
<parameter name="locale" type="text" required="true">
|
||||
<label>MyRenault Location</label>
|
||||
<description>The country (and language combination) that best fits with your MyRenault registered car.</description>
|
||||
<options>
|
||||
<option value="de_AT">Austria</option>
|
||||
<option value="nl_BE">Belgium (Dutch)</option>
|
||||
<option value="fr_BE">Belgium (French)</option>
|
||||
<option value="bg_BG">Bulgaria</option>
|
||||
<option value="hr_HR">Croatia</option>
|
||||
<option value="cs_CZ">Czech</option>
|
||||
<option value="da_DK">Denmark</option>
|
||||
<option value="it_IT">Italy</option>
|
||||
<option value="fi_FI">Finland</option>
|
||||
<option value="fr_FR">France</option>
|
||||
<option value="de_DE">Germany</option>
|
||||
<option value="hu_HU">Hungary</option>
|
||||
<option value="en_IE">Ireland</option>
|
||||
<option value="fr_LU">Luxembourg</option>
|
||||
<option value="es_MX">Mexico</option>
|
||||
<option value="nl_NL">Netherlands</option>
|
||||
<option value="no_NO">Norway</option>
|
||||
<option value="pl_PL">Poland</option>
|
||||
<option value="pt_PT">Portugal</option>
|
||||
<option value="ro_RO">Romania</option>
|
||||
<option value="ru_RU">Russian</option>
|
||||
<option value="sk_SK">Slovakia</option>
|
||||
<option value="sl_SI">Slovenia</option>
|
||||
<option value="es_ES">Spain</option>
|
||||
<option value="sv_SE">Sweden</option>
|
||||
<option value="fr_CH">Switzerland (French)</option>
|
||||
<option value="de_CH">Switzerland (German)</option>
|
||||
<option value="it_CH">Switzerland (Italian)</option>
|
||||
<option value="en_GB">United Kingdom</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="vin" type="text" required="true">
|
||||
<label>VIN</label>
|
||||
<description>Vehicle Identification Number</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" unit="min" min="1">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Interval the car is polled in minutes.</description>
|
||||
<default>10</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Sample Channel Type -->
|
||||
<channel-type id="hvacstatus">
|
||||
<item-type>Switch</item-type>
|
||||
<label>HVAC Status</label>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="image">
|
||||
<item-type>String</item-type>
|
||||
<label>Image URL</label>
|
||||
<description>Image URL of MyRenault</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="odometer">
|
||||
<item-type>Number:Length</item-type>
|
||||
<label>Odometer</label>
|
||||
<description>Total distance travelled</description>
|
||||
<state pattern="%.1f %unit%" readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -285,6 +285,7 @@
|
||||
<module>org.openhab.binding.regoheatpump</module>
|
||||
<module>org.openhab.binding.revogi</module>
|
||||
<module>org.openhab.binding.remoteopenhab</module>
|
||||
<module>org.openhab.binding.renault</module>
|
||||
<module>org.openhab.binding.resol</module>
|
||||
<module>org.openhab.binding.rfxcom</module>
|
||||
<module>org.openhab.binding.rme</module>
|
||||
|
Loading…
Reference in New Issue
Block a user