[renault] Renault more channels and HVAC ON / toggle charge mode (#12095)

* [renault] Add more channels to Renault Binding.
* [renault] Improve example rule to reduce API use.

Signed-off-by: Culnane Douglas <douglas.culnane@extern.a1.at>
This commit is contained in:
Doug Culnane
2022-02-12 20:22:35 +01:00
committed by GitHub
parent 223eb5e24d
commit 605c06107f
9 changed files with 660 additions and 83 deletions

View File

@@ -30,9 +30,18 @@ public class RenaultBindingConstants {
public static final ThingTypeUID THING_TYPE_CAR = new ThingTypeUID(BINDING_ID, "car");
// List of all Channel ids
public static final String CHANNEL_BATTERY_AVAILABLE_ENERGY = "batteryavailableenergy";
public static final String CHANNEL_BATTERY_LEVEL = "batterylevel";
public static final String CHANNEL_CHARGING_MODE = "chargingmode";
public static final String CHANNEL_CHARGING_STATUS = "chargingstatus";
public static final String CHANNEL_CHARGING_REMAINING_TIME = "chargingremainingtime";
public static final String CHANNEL_ESTIMATED_RANGE = "estimatedrange";
public static final String CHANNEL_EXTERNAL_TEMPERATURE = "externaltemperature";
public static final String CHANNEL_HVAC_STATUS = "hvacstatus";
public static final String CHANNEL_HVAC_TARGET_TEMPERATURE = "hvactargettemperature";
public static final String CHANNEL_IMAGE = "image";
public static final String CHANNEL_LOCATION = "location";
public static final String CHANNEL_LOCATION_UPDATED = "locationupdated";
public static final String CHANNEL_ODOMETER = "odometer";
public static final String CHANNEL_PLUG_STATUS = "plugstatus";
}

View File

@@ -27,4 +27,5 @@ public class RenaultConfiguration {
public String locale = "";
public String vin = "";
public int refreshInterval = 10;
public int updateDelay = 30;
}

View File

@@ -30,6 +30,10 @@ import com.google.gson.JsonObject;
@NonNullByDefault
public class Car {
public static final String HVAC_STATUS_ON = "ON";
public static final String HVAC_STATUS_OFF = "OFF";
public static final String HVAC_STATUS_PENDING = "PENDING";
private final Logger logger = LoggerFactory.getLogger(Car.class);
private boolean disableLocation = false;
@@ -37,29 +41,90 @@ public class Car {
private boolean disableCockpit = false;
private boolean disableHvac = false;
private ChargingStatus chargingStatus = ChargingStatus.UNKNOWN;
private ChargingMode chargingMode = ChargingMode.UNKNOWN;
private PlugStatus plugStatus = PlugStatus.UNKNOWN;
private double hvacTargetTemperature = 20.0;
private @Nullable Double batteryLevel;
private @Nullable Double batteryAvailableEnergy;
private @Nullable Integer chargingRemainingTime;
private @Nullable Boolean hvacstatus;
private @Nullable Double odometer;
private @Nullable Double estimatedRange;
private @Nullable String imageURL;
private @Nullable String locationUpdated;
private @Nullable Double gpsLatitude;
private @Nullable Double gpsLongitude;
private @Nullable Double externalTemperature;
public enum ChargingMode {
UNKNOWN,
SCHEDULE_MODE,
ALWAYS_CHARGING
}
public enum PlugStatus {
UNPLUGGED,
PLUGGED,
PLUG_ERROR,
PLUG_UNKNOWN,
UNKNOWN
}
public enum ChargingStatus {
NOT_IN_CHARGE,
WAITING_FOR_A_PLANNED_CHARGE,
CHARGE_ENDED,
WAITING_FOR_CURRENT_CHARGE,
ENERGY_FLAP_OPENED,
CHARGE_IN_PROGRESS,
CHARGE_ERROR,
UNAVAILABLE,
UNKNOWN
}
public void setBatteryStatus(JsonObject responseJson) {
try {
JsonObject attributes = getAttributes(responseJson);
if (attributes != null && attributes.get("batteryLevel") != null) {
batteryLevel = attributes.get("batteryLevel").getAsDouble();
if (attributes != null) {
if (attributes.get("batteryLevel") != null) {
batteryLevel = attributes.get("batteryLevel").getAsDouble();
}
if (attributes.get("batteryAutonomy") != null) {
estimatedRange = attributes.get("batteryAutonomy").getAsDouble();
}
if (attributes.get("plugStatus") != null) {
plugStatus = mapPlugStatus(attributes.get("plugStatus").getAsString());
}
if (attributes.get("chargingStatus") != null) {
chargingStatus = mapChargingStatus(attributes.get("chargingStatus").getAsString());
}
if (attributes.get("batteryAvailableEnergy") != null) {
batteryAvailableEnergy = attributes.get("batteryAvailableEnergy").getAsDouble();
}
if (attributes.get("chargingRemainingTime") != null) {
chargingRemainingTime = attributes.get("chargingRemainingTime").getAsInt();
}
}
} catch (IllegalStateException | ClassCastException e) {
logger.warn("Error {} parsing Battery Status: {}", e.getMessage(), responseJson);
}
}
public void resetHVACStatus() {
this.hvacstatus = null;
}
public void setHVACStatus(JsonObject responseJson) {
try {
JsonObject attributes = getAttributes(responseJson);
if (attributes != null && attributes.get("hvacStatus") != null) {
hvacstatus = attributes.get("hvacStatus").getAsString().equals("on");
if (attributes != null) {
if (attributes.get("hvacStatus") != null) {
hvacstatus = attributes.get("hvacStatus").getAsString().equals("on");
}
if (attributes.get("externalTemperature") != null) {
externalTemperature = attributes.get("externalTemperature").getAsDouble();
}
}
} catch (IllegalStateException | ClassCastException e) {
logger.warn("Error {} parsing HVAC Status: {}", e.getMessage(), responseJson);
@@ -87,6 +152,9 @@ public class Car {
if (attributes.get("gpsLongitude") != null) {
gpsLongitude = attributes.get("gpsLongitude").getAsDouble();
}
if (attributes.get("lastUpdateTime") != null) {
locationUpdated = attributes.get("lastUpdateTime").getAsString();
}
}
} catch (IllegalStateException | ClassCastException e) {
logger.warn("Error {} parsing Location: {}", e.getMessage(), responseJson);
@@ -128,80 +196,112 @@ public class Car {
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;
public @Nullable String getLocationUpdated() {
return locationUpdated;
}
public @Nullable Double getExternalTemperature() {
return externalTemperature;
}
public @Nullable Double getEstimatedRange() {
return estimatedRange;
}
public PlugStatus getPlugStatus() {
return plugStatus;
}
public ChargingStatus getChargingStatus() {
return chargingStatus;
}
public ChargingMode getChargingMode() {
return chargingMode;
}
public @Nullable Integer getChargingRemainingTime() {
return chargingRemainingTime;
}
public @Nullable Double getBatteryAvailableEnergy() {
return batteryAvailableEnergy;
}
public double getHvacTargetTemperature() {
return hvacTargetTemperature;
}
public void setHvacTargetTemperature(double hvacTargetTemperature) {
this.hvacTargetTemperature = hvacTargetTemperature;
}
public void setDisableLocation(boolean disableLocation) {
this.disableLocation = disableLocation;
}
public void setDisableBattery(boolean disableBattery) {
this.disableBattery = disableBattery;
}
public void setDisableCockpit(boolean disableCockpit) {
this.disableCockpit = disableCockpit;
}
public void setDisableHvac(boolean disableHvac) {
this.disableHvac = disableHvac;
}
/**
* Set the charging mode to a known mode.
*
* @param mode
*/
public void setChargeMode(ChargingMode mode) {
switch (mode) {
case SCHEDULE_MODE:
case ALWAYS_CHARGING:
chargingMode = mode;
break;
default:
break;
}
}
private @Nullable JsonObject getAttributes(JsonObject responseJson)
@@ -211,4 +311,44 @@ public class Car {
}
return null;
}
private PlugStatus mapPlugStatus(final String apiPlugState) {
// https://github.com/hacf-fr/renault-api/blob/main/src/renault_api/kamereon/enums.py
switch (apiPlugState) {
case "0":
return PlugStatus.UNPLUGGED;
case "1":
return PlugStatus.PLUGGED;
case "-1":
return PlugStatus.PLUG_ERROR;
case "-2147483648":
return PlugStatus.PLUG_UNKNOWN;
default:
return PlugStatus.UNKNOWN;
}
}
private ChargingStatus mapChargingStatus(final String apiChargeState) {
// https://github.com/hacf-fr/renault-api/blob/main/src/renault_api/kamereon/enums.py
switch (apiChargeState) {
case "0.0":
return ChargingStatus.NOT_IN_CHARGE;
case "0.1":
return ChargingStatus.WAITING_FOR_A_PLANNED_CHARGE;
case "0.2":
return ChargingStatus.CHARGE_ENDED;
case "0.3":
return ChargingStatus.WAITING_FOR_CURRENT_CHARGE;
case "0.4":
return ChargingStatus.ENERGY_FLAP_OPENED;
case "1.0":
return ChargingStatus.CHARGE_IN_PROGRESS;
case "-1.0":
return ChargingStatus.CHARGE_ERROR;
case "-1.1":
return ChargingStatus.UNAVAILABLE;
default:
return ChargingStatus.UNKNOWN;
}
}
}

View File

@@ -20,10 +20,12 @@ 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.client.util.StringContentProvider;
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.Car.ChargingMode;
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;
@@ -46,6 +48,9 @@ import com.google.gson.JsonParser;
@NonNullByDefault
public class MyRenaultHttpSession {
private static final String CHARGING_MODE_SCHEDULE = "schedule_mode";
private static final String CHARGING_MODE_ALWAYS = "always_charging";
private RenaultConfiguration config;
private HttpClient httpClient;
private Constants constants;
@@ -98,6 +103,10 @@ public class MyRenaultHttpSession {
} catch (JsonParseException | ClassCastException | IllegalStateException e) {
throw new RenaultException("Login Error: cookie value not found in JSON response");
}
if (cookieValue == null) {
logger.warn("Login Error: cookie value not found! Response: [{}] {}\n{}", response.getStatus(),
response.getReason(), response.getContentAsString());
}
} else {
logger.warn("Response: [{}] {}\n{}", response.getStatus(), response.getReason(),
response.getContentAsString());
@@ -224,6 +233,75 @@ public class MyRenaultHttpSession {
}
}
public void actionHvacOn(double hvacTargetTemperature)
throws RenaultForbiddenException, RenaultNotImplementedException {
Request request = httpClient
.newRequest(this.constants.getKamereonRootUrl() + "/commerce/v1/accounts/" + kamereonaccountId
+ "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/actions/hvac-start?country="
+ getCountry(config))
.method(HttpMethod.POST).header("Content-type", "application/vnd.api+json")
.header("apikey", this.constants.getKamereonApiKey())
.header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt);
request.content(new StringContentProvider(
"{\"data\":{\"type\":\"HvacStart\",\"attributes\":{\"action\":\"start\",\"targetTemperature\":\""
+ hvacTargetTemperature + "\"}}}",
"utf-8"));
try {
ContentResponse response = request.send();
logger.debug("Kamereon Response HVAC ON: {}", response.getContentAsString());
if (HttpStatus.OK_200 != response.getStatus()) {
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());
}
}
} catch (InterruptedException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
Thread.currentThread().interrupt();
} catch (JsonParseException | TimeoutException | ExecutionException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
}
}
public void actionChargeMode(ChargingMode mode) throws RenaultForbiddenException, RenaultNotImplementedException {
Request request = httpClient
.newRequest(this.constants.getKamereonRootUrl() + "/commerce/v1/accounts/" + kamereonaccountId
+ "/kamereon/kca/car-adapter/v1/cars/" + config.vin + "/actions/charge-mode?country="
+ getCountry(config))
.method(HttpMethod.POST).header("Content-type", "application/vnd.api+json")
.header("apikey", this.constants.getKamereonApiKey())
.header("x-kamereon-authorization", "Bearer " + kamereonToken).header("x-gigya-id_token", jwt);
final String apiMode = ChargingMode.SCHEDULE_MODE.equals(mode) ? CHARGING_MODE_SCHEDULE : CHARGING_MODE_ALWAYS;
request.content(new StringContentProvider(
"{\"data\":{\"type\":\"ChargeMode\",\"attributes\":{\"action\":\"" + apiMode + "\"}}}", "utf-8"));
try {
ContentResponse response = request.send();
logger.debug("Kamereon Response set ChargeMode: {}", response.getContentAsString());
if (HttpStatus.OK_200 != response.getStatus()) {
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());
}
}
} catch (InterruptedException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
Thread.currentThread().interrupt();
} catch (JsonParseException | TimeoutException | ExecutionException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
}
}
private @Nullable JsonObject getKamereonResponse(String path)
throws RenaultForbiddenException, RenaultUpdateException, RenaultNotImplementedException {
Request request = httpClient.newRequest(this.constants.getKamereonRootUrl() + path).method(HttpMethod.GET)
@@ -248,7 +326,10 @@ public class MyRenaultHttpSession {
"Kamereon Response Failed! Error: [" + response.getStatus() + "] " + response.getReason());
}
}
} catch (JsonParseException | InterruptedException | TimeoutException | ExecutionException e) {
} catch (InterruptedException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
Thread.currentThread().interrupt();
} catch (JsonParseException | TimeoutException | ExecutionException e) {
logger.warn("Kamereon Request: {} threw exception: {} ", request.getURI().toString(), e.getMessage());
}
return null;

View File

@@ -15,32 +15,44 @@ 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 static org.openhab.core.library.unit.Units.KILOWATT_HOUR;
import static org.openhab.core.library.unit.Units.MINUTE;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.measure.quantity.Energy;
import javax.measure.quantity.Length;
import javax.measure.quantity.Temperature;
import javax.measure.quantity.Time;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.renault.internal.RenaultBindingConstants;
import org.openhab.binding.renault.internal.RenaultConfiguration;
import org.openhab.binding.renault.internal.api.Car;
import org.openhab.binding.renault.internal.api.Car.ChargingMode;
import org.openhab.binding.renault.internal.api.MyRenaultHttpSession;
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.openhab.core.library.types.DateTimeType;
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.library.unit.SIUnits;
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.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,11 +81,6 @@ public class RenaultHandler extends BaseThingHandler {
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
@@ -104,6 +111,9 @@ public class RenaultHandler extends BaseThingHandler {
}
updateStatus(ThingStatus.UNKNOWN);
updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
// Background initialization:
ScheduledFuture<?> job = pollingJob;
if (job == null || job.isCancelled()) {
@@ -111,6 +121,86 @@ public class RenaultHandler extends BaseThingHandler {
}
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case RenaultBindingConstants.CHANNEL_HVAC_TARGET_TEMPERATURE:
if (!car.isDisableHvac()) {
if (command instanceof RefreshType) {
updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
} else if (command instanceof DecimalType) {
car.setHvacTargetTemperature(((DecimalType) command).doubleValue());
updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
} else if (command instanceof QuantityType) {
@Nullable
QuantityType<Temperature> celsius = ((QuantityType<Temperature>) command)
.toUnit(SIUnits.CELSIUS);
if (celsius != null) {
car.setHvacTargetTemperature(celsius.doubleValue());
}
updateState(CHANNEL_HVAC_TARGET_TEMPERATURE,
new QuantityType<Temperature>(car.getHvacTargetTemperature(), SIUnits.CELSIUS));
}
}
break;
case RenaultBindingConstants.CHANNEL_HVAC_STATUS:
// We can only trigger pre-conditioning of the car.
if (command instanceof StringType && command.toString().equals(Car.HVAC_STATUS_ON)
&& !car.isDisableHvac()) {
final MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
try {
updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_PENDING));
car.resetHVACStatus();
httpSession.initSesssion(car);
httpSession.actionHvacOn(car.getHvacTargetTemperature());
if (pollingJob != null) {
pollingJob.cancel(true);
}
pollingJob = scheduler.scheduleWithFixedDelay(this::getStatus, config.updateDelay,
config.refreshInterval * 60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
logger.warn("Error My Renault Http Session.", e);
Thread.currentThread().interrupt();
} catch (RenaultException | RenaultForbiddenException | RenaultUpdateException
| RenaultNotImplementedException | ExecutionException | TimeoutException e) {
logger.warn("Error My Renault Http Session.", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
break;
case RenaultBindingConstants.CHANNEL_CHARGING_MODE:
if (command instanceof StringType) {
try {
ChargingMode newMode = ChargingMode.valueOf(command.toString());
if (!ChargingMode.UNKNOWN.equals(newMode)) {
MyRenaultHttpSession httpSession = new MyRenaultHttpSession(this.config, httpClient);
try {
httpSession.initSesssion(car);
httpSession.actionChargeMode(newMode);
car.setChargeMode(newMode);
updateState(CHANNEL_CHARGING_MODE, new StringType(newMode.toString()));
} catch (InterruptedException e) {
logger.warn("Error My Renault Http Session.", e);
Thread.currentThread().interrupt();
} catch (RenaultException | RenaultForbiddenException | RenaultUpdateException
| RenaultNotImplementedException | ExecutionException | TimeoutException e) {
logger.warn("Error My Renault Http Session.", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
e.getMessage());
}
}
} catch (IllegalArgumentException e) {
logger.warn("Invalid ChargingMode {}.", command.toString());
return;
}
}
default:
break;
}
}
@Override
public void dispose() {
ScheduledFuture<?> job = pollingJob;
@@ -126,8 +216,10 @@ public class RenaultHandler extends BaseThingHandler {
try {
httpSession.initSesssion(car);
updateStatus(ThingStatus.ONLINE);
} catch (InterruptedException e) {
logger.warn("Error My Renault Http Session.", e);
Thread.currentThread().interrupt();
} catch (Exception e) {
httpSession = null;
logger.warn("Error My Renault Http Session.", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
@@ -148,8 +240,17 @@ public class RenaultHandler extends BaseThingHandler {
try {
httpSession.getHvacStatus(car);
Boolean hvacstatus = car.getHvacstatus();
if (hvacstatus != null) {
updateState(CHANNEL_HVAC_STATUS, OnOffType.from(hvacstatus.booleanValue()));
if (hvacstatus == null) {
updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_PENDING));
} else if (hvacstatus.booleanValue()) {
updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_ON));
} else {
updateState(CHANNEL_HVAC_STATUS, new StringType(Car.HVAC_STATUS_OFF));
}
Double externalTemperature = car.getExternalTemperature();
if (externalTemperature != null) {
updateState(CHANNEL_EXTERNAL_TEMPERATURE,
new QuantityType<Temperature>(externalTemperature.doubleValue(), SIUnits.CELSIUS));
}
} catch (RenaultNotImplementedException e) {
car.setDisableHvac(true);
@@ -168,9 +269,13 @@ public class RenaultHandler extends BaseThingHandler {
updateState(CHANNEL_LOCATION, new PointType(new DecimalType(latitude.doubleValue()),
new DecimalType(longitude.doubleValue())));
}
String locationUpdated = car.getLocationUpdated();
if (locationUpdated != null) {
updateState(CHANNEL_LOCATION_UPDATED, new DateTimeType(locationUpdated));
}
} catch (RenaultNotImplementedException e) {
car.setDisableLocation(true);
} catch (RenaultForbiddenException | RenaultUpdateException e) {
} catch (IllegalArgumentException | RenaultForbiddenException | RenaultUpdateException e) {
}
}
}
@@ -194,10 +299,27 @@ public class RenaultHandler extends BaseThingHandler {
if (!car.isDisableBattery()) {
try {
httpSession.getBatteryStatus(car);
updateState(CHANNEL_PLUG_STATUS, new StringType(car.getPlugStatus().name()));
updateState(CHANNEL_CHARGING_STATUS, new StringType(car.getChargingStatus().name()));
Double batteryLevel = car.getBatteryLevel();
if (batteryLevel != null) {
updateState(CHANNEL_BATTERY_LEVEL, new DecimalType(batteryLevel.doubleValue()));
}
Double estimatedRange = car.getEstimatedRange();
if (estimatedRange != null) {
updateState(CHANNEL_ESTIMATED_RANGE,
new QuantityType<Length>(estimatedRange.doubleValue(), KILO(METRE)));
}
Double batteryAvailableEnergy = car.getBatteryAvailableEnergy();
if (batteryAvailableEnergy != null) {
updateState(CHANNEL_BATTERY_AVAILABLE_ENERGY,
new QuantityType<Energy>(batteryAvailableEnergy.doubleValue(), KILOWATT_HOUR));
}
Integer chargingRemainingTime = car.getChargingRemainingTime();
if (chargingRemainingTime != null) {
updateState(CHANNEL_CHARGING_REMAINING_TIME,
new QuantityType<Time>(chargingRemainingTime.doubleValue(), MINUTE));
}
} catch (RenaultNotImplementedException e) {
car.setDisableBattery(true);
} catch (RenaultForbiddenException | RenaultUpdateException e) {

View File

@@ -45,13 +45,50 @@ thing-type.config.renault.car.myRenaultPassword.label = MyRenault Password
thing-type.config.renault.car.myRenaultUsername.label = MyRenault Username
thing-type.config.renault.car.refreshInterval.label = Refresh Interval
thing-type.config.renault.car.refreshInterval.description = Interval the car is polled in minutes.
thing-type.config.renault.car.updateDelay.label = Update Delay
thing-type.config.renault.car.updateDelay.description = How long to wait for commands to reach car and update to server in seconds.
thing-type.config.renault.car.vin.label = VIN
thing-type.config.renault.car.vin.description = Vehicle Identification Number
# channel types
channel-type.renault.hvacstatus.label = HVAC Status
channel-type.renault.batteryavailableenergy.label = Battery Energy Available
channel-type.renault.chargingmode.label = Charging Mode
channel-type.renault.chargingmode.state.option.UNKNOWN = Unknown
channel-type.renault.chargingmode.state.option.SCHEDULE_MODE = Schedule mode
channel-type.renault.chargingmode.state.option.ALWAYS_CHARGING = Instant charge
channel-type.renault.chargingremainingtime.label = Charging Time Remaining
channel-type.renault.chargingstatus.label = Charging Status
channel-type.renault.chargingstatus.state.option.NOT_IN_CHARGE = Not charging
channel-type.renault.chargingstatus.state.option.WAITING_FOR_A_PLANNED_CHARGE = Waiting for schedule
channel-type.renault.chargingstatus.state.option.CHARGE_ENDED = Charge ended
channel-type.renault.chargingstatus.state.option.WAITING_FOR_CURRENT_CHARGE = Waiting for charge
channel-type.renault.chargingstatus.state.option.ENERGY_FLAP_OPENED = Plug flap opened
channel-type.renault.chargingstatus.state.option.CHARGE_IN_PROGRESS = Charge in progress
channel-type.renault.chargingstatus.state.option.CHARGE_ERROR = Charge error
channel-type.renault.chargingstatus.state.option.UNAVAILABLE = Unavailable
channel-type.renault.chargingstatus.state.option.UNKNOWN = Unknown
channel-type.renault.estimatedrange.label = Estimated Range
channel-type.renault.estimatedrange.description = Estimated range of the car.
channel-type.renault.externaltemperature.label = External Temperature
channel-type.renault.externaltemperature.description = Temperature outside of the car
channel-type.renault.hvacstatus.label = HVAC Status (ON | OFF | PENDING)
channel-type.renault.hvacstatus.state.option.ON = On
channel-type.renault.hvacstatus.state.option.PENDING = Pending
channel-type.renault.hvacstatus.state.option.OFF = Off
channel-type.renault.hvactargettemperature.label = HVAC Target Temperature
channel-type.renault.hvactargettemperature.description = HVAC target temperature (19 to 21)
channel-type.renault.image.label = Image URL
channel-type.renault.image.description = Image URL of MyRenault
channel-type.renault.locationupdated.label = Location Update
channel-type.renault.locationupdated.description = Timestamp of the last location update
channel-type.renault.locationupdated.state.pattern = %1$tH:%1$tM %1$td.%1$tm.%1$tY
channel-type.renault.odometer.label = Odometer
channel-type.renault.odometer.description = Total distance travelled
channel-type.renault.plugstatus.label = Plug Status
channel-type.renault.plugstatus.description = Status of charging plug.
channel-type.renault.plugstatus.state.option.UNPLUGGED = Unplugged
channel-type.renault.plugstatus.state.option.PLUGGED = Plugged
channel-type.renault.plugstatus.state.option.PLUG_ERROR = Plug error
channel-type.renault.plugstatus.state.option.PLUG_UNKNOWN = Plug unknown
channel-type.renault.plugstatus.state.option.UNKNOWN = Unknown

View File

@@ -11,10 +11,19 @@
<channels>
<channel id="batterylevel" typeId="system.battery-level"/>
<channel id="batteryavailableenergy" typeId="batteryavailableenergy"/>
<channel id="plugstatus" typeId="plugstatus"/>
<channel id="chargingstatus" typeId="chargingstatus"/>
<channel id="chargingmode" typeId="chargingmode"/>
<channel id="chargingremainingtime" typeId="chargingremainingtime"/>
<channel id="estimatedrange" typeId="estimatedrange"/>
<channel id="odometer" typeId="odometer"/>
<channel id="hvacstatus" typeId="hvacstatus"/>
<channel id="hvactargettemperature" typeId="hvactargettemperature"/>
<channel id="externaltemperature" typeId="externaltemperature"/>
<channel id="image" typeId="image"/>
<channel id="location" typeId="system.location"/>
<channel id="odometer" typeId="odometer"/>
<channel id="locationupdated" typeId="locationupdated"/>
</channels>
<config-description>
@@ -69,20 +78,72 @@
<description>Interval the car is polled in minutes.</description>
<default>10</default>
</parameter>
<parameter name="updateDelay" type="integer" unit="s" min="5" max="120">
<label>Update Delay</label>
<description>How long to wait for commands to reach car and update to server in seconds.</description>
<default>30</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 id="batteryavailableenergy">
<item-type>Number:Energy</item-type>
<label>Battery Energy Available</label>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="image">
<channel-type id="plugstatus">
<item-type>String</item-type>
<label>Image URL</label>
<description>Image URL of MyRenault</description>
<state readOnly="true"></state>
<label>Plug Status</label>
<description>Status of charging plug.</description>
<state readOnly="true">
<options>
<option value="UNPLUGGED">Unplugged</option>
<option value="PLUGGED">Plugged</option>
<option value="PLUG_ERROR">Plug error</option>
<option value="PLUG_UNKNOWN">Plug unknown</option>
<option value="UNKNOWN">Unknown</option>
</options>
</state>
</channel-type>
<channel-type id="chargingstatus">
<item-type>String</item-type>
<label>Charging Status</label>
<state readOnly="true">
<options>
<option value="NOT_IN_CHARGE">Not charging</option>
<option value="WAITING_FOR_A_PLANNED_CHARGE">Waiting for schedule</option>
<option value="CHARGE_ENDED">Charge ended</option>
<option value="WAITING_FOR_CURRENT_CHARGE">Waiting for charge</option>
<option value="ENERGY_FLAP_OPENED">Plug flap opened</option>
<option value="CHARGE_IN_PROGRESS">Charge in progress</option>
<option value="CHARGE_ERROR">Charge error</option>
<option value="UNAVAILABLE">Unavailable</option>
<option value="UNKNOWN">Unknown</option>
</options>
</state>
</channel-type>
<channel-type id="chargingmode">
<item-type>String</item-type>
<label>Charging Mode</label>
<state readOnly="false">
<options>
<option value="UNKNOWN">Unknown</option>
<option value="SCHEDULE_MODE">Schedule mode</option>
<option value="ALWAYS_CHARGING">Instant charge</option>
</options>
</state>
</channel-type>
<channel-type id="chargingremainingtime">
<item-type>Number:Time</item-type>
<label>Charging Time Remaining</label>
<category>Time</category>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="estimatedrange">
<item-type>Number:Length</item-type>
<label>Estimated Range</label>
<description>Estimated range of the car.</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="odometer">
<item-type>Number:Length</item-type>
@@ -90,5 +151,42 @@
<description>Total distance travelled</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="hvacstatus">
<item-type>String</item-type>
<label>HVAC Status (ON | OFF | PENDING)</label>
<state readOnly="false">
<options>
<option value="ON">On</option>
<option value="PENDING">Pending</option>
<option value="OFF">Off</option>
</options>
</state>
</channel-type>
<channel-type id="hvactargettemperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>HVAC Target Temperature</label>
<description>HVAC target temperature (19 to 21)</description>
<category>Temperature</category>
<state pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="externaltemperature" advanced="true">
<item-type>Number:Temperature</item-type>
<label>External Temperature</label>
<description>Temperature outside of the car</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" 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="locationupdated">
<item-type>DateTime</item-type>
<label>Location Update</label>
<description>Timestamp of the last location update</description>
<state pattern="%1$tH:%1$tM %1$td.%1$tm.%1$tY" readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>