[tesla] Adapt binding to changed API from Tesla backend (#14922)

* Adapt binding to changed API from Tesla backend

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer 2023-05-02 21:49:06 +02:00 committed by GitHub
parent fb16e90168
commit f607dde3a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 301 deletions

View File

@ -205,64 +205,64 @@ Bridge tesla:account:myaccount "My Tesla Account" [ refreshToken="xxxx" ] {
demo.items:
```java
DateTime TeslaEventstamp {channel="model3:myaccount:mycar:eventstamp"}
String TeslaState {channel="model3:myaccount:mycar:state"}
Number TeslaSpeed {channel="model3:myaccount:mycar:speed"}
String TeslaShiftState {channel="model3:myaccount:mycar:shiftstate"}
Number TeslaOdometer {channel="model3:myaccount:mycar:odometer"}
Number TeslaRange {channel="model3:myaccount:mycar:range"}
DateTime TeslaEventstamp {channel="account:model3:myaccount:mycar:eventstamp"}
String TeslaState {channel="account:model3:myaccount:mycar:state"}
Number TeslaSpeed {channel="account:model3:myaccount:mycar:speed"}
String TeslaShiftState {channel="account:model3:myaccount:mycar:shiftstate"}
Number TeslaOdometer {channel="account:model3:myaccount:mycar:odometer"}
Number TeslaRange {channel="account:model3:myaccount:mycar:range"}
Number TeslaBatteryLevel {channel="model3:myaccount:mycar:batterylevel"}
Number TeslaPower {channel="model3:myaccount:mycar:power"}
Number TeslaBatteryCurrent {channel="model3:myaccount:mycar:batterycurrent"}
Number TeslaBatteryRange {channel="model3:myaccount:mycar:batteryrange"}
Number TeslaEstBatteryRange {channel="model3:myaccount:mycar:estimatedbatteryrange"}
Number TeslaIdealBatteryRange {channel="model3:myaccount:mycar:idealbatteryrange"}
Number TeslaUsableBatteryLevel {channel="model3:myaccount:mycar:usablebatterylevel"}
Switch TeslaPreconditioning {channel="model3:myaccount:mycar:preconditioning"}
Number TeslaBatteryLevel {channel="account:model3:myaccount:mycar:batterylevel"}
Number TeslaPower {channel="account:model3:myaccount:mycar:power"}
Number TeslaBatteryCurrent {channel="account:model3:myaccount:mycar:batterycurrent"}
Number TeslaBatteryRange {channel="account:model3:myaccount:mycar:batteryrange"}
Number TeslaEstBatteryRange {channel="account:model3:myaccount:mycar:estimatedbatteryrange"}
Number TeslaIdealBatteryRange {channel="account:model3:myaccount:mycar:idealbatteryrange"}
Number TeslaUsableBatteryLevel {channel="account:model3:myaccount:mycar:usablebatterylevel"}
Switch TeslaPreconditioning {channel="account:model3:myaccount:mycar:preconditioning"}
Switch TeslaCharge {channel="model3:myaccount:mycar:charge"}
Switch TeslaChargeToMax {channel="model3:myaccount:mycar:chargetomax"}
Switch TeslaCharge {channel="account:model3:myaccount:mycar:charge"}
Switch TeslaChargeToMax {channel="account:model3:myaccount:mycar:chargetomax"}
Dimmer TeslaChargeLimit {channel="model3:myaccount:mycar:chargelimit"}
Number TeslaChargeRate {channel="model3:myaccount:mycar:chargerate"}
String TeslaChargingState {channel="model3:myaccount:mycar:chargingstate"}
Number TeslaChargerPower {channel="model3:myaccount:mycar:chargerpower"}
Number TeslaTimeToFullCharge {channel="model3:myaccount:mycar:timetofullcharge"}
Number TeslaMaxCharges {channel="model3:myaccount:mycar:maxcharges"}
Dimmer TeslaChargeLimit {channel="account:model3:myaccount:mycar:chargelimit"}
Number TeslaChargeRate {channel="account:model3:myaccount:mycar:chargerate"}
String TeslaChargingState {channel="account:model3:myaccount:mycar:chargingstate"}
Number TeslaChargerPower {channel="account:model3:myaccount:mycar:chargerpower"}
Number TeslaTimeToFullCharge {channel="account:model3:myaccount:mycar:timetofullcharge"}
Number TeslaMaxCharges {channel="account:model3:myaccount:mycar:maxcharges"}
Number TeslaChargerVoltage {channel="model3:myaccount:mycar:chargervoltage"}
Number TeslaChargerPower {channel="model3:myaccount:mycar:chargerpower"}
Number TeslaChargerCurrent {channel="model3:myaccount:mycar:chargercurrent"}
Number TeslaChargerVoltage {channel="account:model3:myaccount:mycar:chargervoltage"}
Number TeslaChargerPower {channel="account:model3:myaccount:mycar:chargerpower"}
Number TeslaChargerCurrent {channel="account:model3:myaccount:mycar:chargercurrent"}
DateTime TeslaScheduledChargingStart {channel="model3:myaccount:mycar:scheduledchargingstart"}
Dimmer TeslaSoC {channel="model3:myaccount:mycar:soc"}
DateTime TeslaScheduledChargingStart {channel="account:model3:myaccount:mycar:scheduledchargingstart"}
Dimmer TeslaSoC {channel="account:model3:myaccount:mycar:soc"}
Switch TeslaDoorLock {channel="model3:myaccount:mycar:doorlock"}
Switch TeslaHorn {channel="model3:myaccount:mycar:honkhorn"}
Switch TeslaStart {channel="model3:myaccount:mycar:remotestart"}
Switch TeslaSentry {channel="model3:myaccount:mycar:sentrymode"}
Switch TeslaLights {channel="model3:myaccount:mycar:flashlights"}
Switch TeslaValet {channel="model3:myaccount:mycar:valetmode"}
Switch TeslaDoorLock {channel="account:model3:myaccount:mycar:doorlock"}
Switch TeslaHorn {channel="account:model3:myaccount:mycar:honkhorn"}
Switch TeslaStart {channel="account:model3:myaccount:mycar:remotestart"}
Switch TeslaSentry {channel="account:model3:myaccount:mycar:sentrymode"}
Switch TeslaLights {channel="account:model3:myaccount:mycar:flashlights"}
Switch TeslaValet {channel="account:model3:myaccount:mycar:valetmode"}
Switch TeslaWakeup {channel="model3:myaccount:mycar:wakeup"}
Switch TeslaWakeup {channel="account:model3:myaccount:mycar:wakeup"}
Switch TeslaBatteryHeater {channel="model3:myaccount:mycar:batteryheater"}
Switch TeslaFrontDefrost {channel="model3:myaccount:mycar:frontdefroster"}
Switch TeslaRearDefrost {channel="model3:myaccount:mycar:reardefroster"}
Switch TeslaLeftSeatHeater {channel="model3:myaccount:mycar:leftseatheater"}
Switch TeslaRightSeatHeater {channel="model3:myaccount:mycar:rightseatheater"}
Switch TeslaBatteryHeater {channel="account:model3:myaccount:mycar:batteryheater"}
Switch TeslaFrontDefrost {channel="account:model3:myaccount:mycar:frontdefroster"}
Switch TeslaRearDefrost {channel="account:model3:myaccount:mycar:reardefroster"}
Switch TeslaLeftSeatHeater {channel="account:model3:myaccount:mycar:leftseatheater"}
Switch TeslaRightSeatHeater {channel="account:model3:myaccount:mycar:rightseatheater"}
Switch TeslaHomelink {channel="model3:myaccount:mycar:homelink"}
Location TeslaLocation {channel="model3:myaccount:mycar:location"}
Number TeslaHeading {channel="model3:myaccount:mycar:heading"}
DateTime TeslaLocationTime {channel="model3:myaccount:mycar:gpstimestamp"}
Switch TeslaHomelink {channel="account:model3:myaccount:mycar:homelink"}
Location TeslaLocation {channel="account:model3:myaccount:mycar:location"}
Number TeslaHeading {channel="account:model3:myaccount:mycar:heading"}
DateTime TeslaLocationTime {channel="account:model3:myaccount:mycar:gpstimestamp"}
Switch TeslaAutoconditioning {channel="model3:myaccount:mycar:autoconditioning"}
Number:Temperature TeslaTemperature {channel="model3:myaccount:mycar:temperature"}
Number:Temperature TeslaTemperatureCombined {channel="model3:myaccount:mycar:combinedtemp"}
Number:Temperature TeslaInsideTemperature {channel="model3:myaccount:mycar:insidetemp"}
Number:Temperature TeslaOutsideTemperature {channel="model3:myaccount:mycar:outsidetemp"}
Switch TeslaAutoconditioning {channel="account:model3:myaccount:mycar:autoconditioning"}
Number:Temperature TeslaTemperature {channel="account:model3:myaccount:mycar:temperature"}
Number:Temperature TeslaTemperatureCombined {channel="account:model3:myaccount:mycar:combinedtemp"}
Number:Temperature TeslaInsideTemperature {channel="account:model3:myaccount:mycar:insidetemp"}
Number:Temperature TeslaOutsideTemperature {channel="account:model3:myaccount:mycar:outsidetemp"}
```
demo.sitemap:
@ -337,23 +337,7 @@ sitemap main label="Main"
}
Frame
{
Switch label="State" item=nTeslaState_chart icon=line mappings=[0="Hide", 1="Hour", 2="Day", 3="Week", 4="Month"]
Chart item=nTeslaState period=h refresh=30000 visibility=[nTeslaState_chart==1]
Chart item=nTeslaState period=D refresh=30000 visibility=[nTeslaState_chart==2]
Chart item=nTeslaState period=W refresh=30000 visibility=[nTeslaState_chart==3]
Chart item=nTeslaState period=M refresh=30000 visibility=[nTeslaState_chart==4]
}
Frame
{
Switch label="Battery" item=TeslaBatteryLevel_chart icon=line mappings=[0="Hide", 1="Hour", 2="Day", 3="Week", 4="Month"]
Chart item=TeslaUsableBatteryLevel period=h refresh=30000 visibility=[TeslaBatteryLevel_chart==1]
Chart item=TeslaUsableBatteryLevel period=D refresh=30000 visibility=[TeslaBatteryLevel_chart==2]
Chart item=TeslaUsableBatteryLevel period=W refresh=30000 visibility=[TeslaBatteryLevel_chart==3]
Chart item=TeslaUsableBatteryLevel period=M refresh=30000 visibility=[TeslaBatteryLevel_chart==4]
}
Frame
{
Mapview item=TeslaLocation height=10 icon=location
Mapview item=TeslaLocation height=10
}
}
}

View File

@ -28,7 +28,7 @@ public class TeslaBindingConstants {
public static final String API_NAME = "Tesla Client API";
public static final String API_VERSION = "api/1/";
public static final String PATH_COMMAND = "command/{cmd}";
public static final String PATH_DATA_REQUEST = "data_request/{cmd}";
public static final String PATH_DATA_REQUEST = "vehicle_data";
public static final String PATH_VEHICLE_ID = "/{vid}/";
public static final String PATH_WAKE_UP = "wake_up";
public static final String PATH_ACCESS_TOKEN = "oauth/token";
@ -71,15 +71,6 @@ public class TeslaBindingConstants {
public static final String COMMAND_WAKE_UP = "wake_up";
public static final String DATA_THROTTLE = "datathrottle";
// Tesla REST API vehicle states
public static final String CHARGE_STATE = "charge_state";
public static final String CLIMATE_STATE = "climate_state";
public static final String DRIVE_STATE = "drive_state";
public static final String GUI_STATE = "gui_settings";
public static final String MOBILE_ENABLED_STATE = "mobile_enabled";
public static final String VEHICLE_STATE = "vehicle_state";
public static final String VEHICLE_CONFIG = "vehicle_config";
public static final String BINDING_ID = "tesla";
// List of all Thing Type UIDs

View File

@ -514,7 +514,7 @@ public class TeslaChannelSelectorProxy {
}
},
MANAGED_CHARGING_START("managed_charging_start_time", "managedchargingstart", StringType.class, false),
MOBILE_ENABLED(TeslaBindingConstants.MOBILE_ENABLED_STATE, "mobileenabled", OnOffType.class, false) {
MOBILE_ENABLED("mobile_enabled", "mobileenabled", OnOffType.class, false) {
@Override
public State getState(String s, TeslaChannelSelectorProxy proxy, Map<String, String> properties) {
if ("true".equals(s)) {

View File

@ -36,6 +36,7 @@ import org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.binding.tesla.internal.discovery.TeslaVehicleDiscoveryService;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
import org.openhab.binding.tesla.internal.protocol.VehicleData;
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
@ -225,10 +226,10 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
for (Vehicle vehicle : vehicleArray) {
String responseString = invokeAndParse(vehicle.id, VEHICLE_CONFIG, null, dataRequestTarget, 0);
String responseString = invokeAndParse(vehicle.id, null, null, dataRequestTarget, 0);
VehicleConfig vehicleConfig = null;
if (responseString != null && !responseString.isBlank()) {
vehicleConfig = gson.fromJson(responseString, VehicleConfig.class);
vehicleConfig = gson.fromJson(responseString, VehicleData.class).vehicle_config;
}
for (VehicleListener listener : vehicleListeners) {
listener.vehicleFound(vehicle, vehicleConfig);

View File

@ -23,6 +23,7 @@ import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
@ -48,6 +49,7 @@ import org.openhab.binding.tesla.internal.protocol.DriveState;
import org.openhab.binding.tesla.internal.protocol.Event;
import org.openhab.binding.tesla.internal.protocol.GUIState;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleData;
import org.openhab.binding.tesla.internal.protocol.VehicleState;
import org.openhab.binding.tesla.internal.throttler.QueueChannelThrottler;
import org.openhab.binding.tesla.internal.throttler.Rate;
@ -140,8 +142,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
protected QueueChannelThrottler stateThrottler;
protected TeslaChannelSelectorProxy teslaChannelSelectorProxy = new TeslaChannelSelectorProxy();
protected Thread eventThread;
protected ScheduledFuture<?> fastStateJob;
protected ScheduledFuture<?> slowStateJob;
protected ScheduledFuture<?> stateJob;
protected WebSocketFactory webSocketFactory;
private final Gson gson = new Gson();
@ -181,13 +182,8 @@ public class TeslaVehicleHandler extends BaseThingHandler {
stateThrottler = new QueueChannelThrottler(firstRate, scheduler, channels);
stateThrottler.addRate(secondRate);
if (fastStateJob == null || fastStateJob.isCancelled()) {
fastStateJob = scheduler.scheduleWithFixedDelay(fastStateRunnable, 0, FAST_STATUS_REFRESH_INTERVAL,
TimeUnit.MILLISECONDS);
}
if (slowStateJob == null || slowStateJob.isCancelled()) {
slowStateJob = scheduler.scheduleWithFixedDelay(slowStateRunnable, 0, SLOW_STATUS_REFRESH_INTERVAL,
if (stateJob == null || stateJob.isCancelled()) {
stateJob = scheduler.scheduleWithFixedDelay(stateRunnable, 0, SLOW_STATUS_REFRESH_INTERVAL,
TimeUnit.MILLISECONDS);
}
@ -207,14 +203,9 @@ public class TeslaVehicleHandler extends BaseThingHandler {
logger.trace("Disposing the Tesla handler for {}", getThing().getUID());
lock.lock();
try {
if (fastStateJob != null && !fastStateJob.isCancelled()) {
fastStateJob.cancel(true);
fastStateJob = null;
}
if (slowStateJob != null && !slowStateJob.isCancelled()) {
slowStateJob.cancel(true);
slowStateJob = null;
if (stateJob != null && !stateJob.isCancelled()) {
stateJob.cancel(true);
stateJob = null;
}
if (eventThread != null && !eventThread.isInterrupted()) {
@ -494,7 +485,8 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
public void requestData(String command, String payLoad) {
if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
if (COMMAND_WAKE_UP.equals(command) || isAwake()
|| (!"vehicleData".equals(command) && allowWakeUpForCommands)) {
Request request = account.newRequest(this, command, payLoad, account.dataRequestTarget, false);
if (stateThrottler != null) {
stateThrottler.submit(DATA_THROTTLE, request);
@ -527,11 +519,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
public void requestAllData() {
requestData(DRIVE_STATE);
requestData(VEHICLE_STATE);
requestData(CHARGE_STATE);
requestData(CLIMATE_STATE);
requestData(GUI_STATE);
requestData("vehicleData", null);
}
protected boolean isAwake() {
@ -591,7 +579,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
}
if (vehicleState.homelink_nearby) {
if (vehicleState != null && vehicleState.homelink_nearby) {
computedInactivityPeriod = MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT;
logger.debug("Car is at home. Movement or drive state threshold is {} min.",
MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT);
@ -671,21 +659,18 @@ public class TeslaVehicleHandler extends BaseThingHandler {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("percent", percent);
sendCommand(COMMAND_SET_CHARGE_LIMIT, gson.toJson(payloadObject), account.commandTarget);
requestData(CHARGE_STATE);
}
public void setChargingAmps(int amps) {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("charging_amps", amps);
sendCommand(COMMAND_SET_CHARGING_AMPS, gson.toJson(payloadObject), account.commandTarget);
requestData(CHARGE_STATE);
}
public void setSentryMode(boolean b) {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("on", b);
sendCommand(COMMAND_SET_SENTRY_MODE, gson.toJson(payloadObject), account.commandTarget);
requestData(VEHICLE_STATE);
}
public void setSunroof(String state) {
@ -693,7 +678,6 @@ public class TeslaVehicleHandler extends BaseThingHandler {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("state", state);
sendCommand(COMMAND_SUN_ROOF, gson.toJson(payloadObject), account.commandTarget);
requestData(VEHICLE_STATE);
} else {
logger.warn("Ignoring invalid command '{}' for sunroof.", state);
}
@ -714,7 +698,6 @@ public class TeslaVehicleHandler extends BaseThingHandler {
payloadObject.addProperty("driver_temp", driverTemperature);
payloadObject.addProperty("passenger_temp", passenegerTemperature);
sendCommand(COMMAND_SET_TEMP, gson.toJson(payloadObject), account.commandTarget);
requestData(CLIMATE_STATE);
}
public void setCombinedTemperature(float temperature) {
@ -733,14 +716,12 @@ public class TeslaVehicleHandler extends BaseThingHandler {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("which_trunk", "front");
sendCommand(COMMAND_ACTUATE_TRUNK, gson.toJson(payloadObject), account.commandTarget);
requestData(VEHICLE_STATE);
}
public void openTrunk() {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("which_trunk", "rear");
sendCommand(COMMAND_ACTUATE_TRUNK, gson.toJson(payloadObject), account.commandTarget);
requestData(VEHICLE_STATE);
}
public void closeTrunk() {
@ -754,22 +735,18 @@ public class TeslaVehicleHandler extends BaseThingHandler {
payloadObject.addProperty("password", String.format("%04d", pin));
}
sendCommand(COMMAND_SET_VALET_MODE, gson.toJson(payloadObject), account.commandTarget);
requestData(VEHICLE_STATE);
}
public void resetValetPin() {
sendCommand(COMMAND_RESET_VALET_PIN, account.commandTarget);
requestData(VEHICLE_STATE);
}
public void setMaxRangeCharging(boolean b) {
sendCommand(b ? COMMAND_CHARGE_MAX : COMMAND_CHARGE_STD, account.commandTarget);
requestData(CHARGE_STATE);
}
public void charge(boolean b) {
sendCommand(b ? COMMAND_CHARGE_START : COMMAND_CHARGE_STOP, account.commandTarget);
requestData(CHARGE_STATE);
}
public void flashLights() {
@ -782,17 +759,14 @@ public class TeslaVehicleHandler extends BaseThingHandler {
public void openChargePort() {
sendCommand(COMMAND_OPEN_CHARGE_PORT, account.commandTarget);
requestData(CHARGE_STATE);
}
public void lockDoors(boolean b) {
sendCommand(b ? COMMAND_DOOR_LOCK : COMMAND_DOOR_UNLOCK, account.commandTarget);
requestData(VEHICLE_STATE);
}
public void autoConditioning(boolean b) {
sendCommand(b ? COMMAND_AUTO_COND_START : COMMAND_AUTO_COND_STOP, account.commandTarget);
requestData(CLIMATE_STATE);
}
public void wakeUp() {
@ -854,194 +828,138 @@ public class TeslaVehicleHandler extends BaseThingHandler {
public void parseAndUpdate(String request, String payLoad, String result) {
final double locationThreshold = .0000001;
JsonObject jsonObject = null;
try {
if (request != null && result != null && !"null".equals(result)) {
updateStatus(ThingStatus.ONLINE);
// first, update state objects
switch (request) {
case DRIVE_STATE: {
driveState = gson.fromJson(result, DriveState.class);
if ("queryVehicle".equals(request)) {
if (vehicle != null) {
logger.debug("Vehicle state is {}", vehicle.state);
updateState(TeslaChannelSelector.STATE.getChannelID(), new StringType(vehicle.state));
} else {
logger.debug("Vehicle state is initializing or unknown");
return;
}
if (Math.abs(lastLatitude - driveState.latitude) > locationThreshold
|| Math.abs(lastLongitude - driveState.longitude) > locationThreshold) {
logger.debug("Vehicle moved, resetting last location timestamp");
if (vehicle != null && "asleep".equals(vehicle.state)) {
logger.debug("Vehicle is asleep.");
return;
}
lastLatitude = driveState.latitude;
lastLongitude = driveState.longitude;
if (vehicle != null && !lastState.equals(vehicle.state)) {
lastState = vehicle.state;
// in case vehicle changed to awake, refresh all data
if (isAwake()) {
logger.debug("Vehicle is now awake, updating all data");
lastLocationChangeTimestamp = System.currentTimeMillis();
}
logger.trace("Drive state: {}", driveState.shift_state);
if ((driveState.shift_state == null) && (lastValidDriveStateNotNull)) {
logger.debug("Set NULL shiftstate time");
lastValidDriveStateNotNull = false;
lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
} else if (driveState.shift_state != null) {
logger.trace("Clear NULL shiftstate time");
lastValidDriveStateNotNull = true;
requestAllData();
}
break;
setActive();
}
case GUI_STATE: {
guiState = gson.fromJson(result, GUIState.class);
break;
}
case VEHICLE_STATE: {
vehicleState = gson.fromJson(result, VehicleState.class);
break;
}
case CHARGE_STATE: {
chargeState = gson.fromJson(result, ChargeState.class);
if (isCharging()) {
updateState(CHANNEL_CHARGE, OnOffType.ON);
} else {
updateState(CHANNEL_CHARGE, OnOffType.OFF);
}
break;
}
case CLIMATE_STATE: {
climateState = gson.fromJson(result, ClimateState.class);
BigDecimal avgtemp = roundBigDecimal(new BigDecimal(
(climateState.driver_temp_setting + climateState.passenger_temp_setting) / 2.0f));
updateState(CHANNEL_COMBINED_TEMP, new QuantityType<>(avgtemp, SIUnits.CELSIUS));
break;
}
case "queryVehicle": {
if (vehicle != null) {
logger.debug("Vehicle state is {}", vehicle.state);
} else {
logger.debug("Vehicle state is initializing or unknown");
break;
}
// reset timestamp if elapsed and set inactive to false
if (isInactive && lastStateTimestamp + (API_SLEEP_INTERVAL_MINUTES * 60 * 1000) < System
.currentTimeMillis()) {
logger.debug("Vehicle did not fall asleep within sleep period, checking again");
setActive();
} else {
boolean wasInactive = isInactive;
isInactive = !isCharging() && !notReadyForSleep();
if (vehicle != null && "asleep".equals(vehicle.state)) {
logger.debug("Vehicle is asleep.");
break;
}
if (vehicle != null && !lastState.equals(vehicle.state)) {
lastState = vehicle.state;
// in case vehicle changed to awake, refresh all data
if (isAwake()) {
logger.debug("Vehicle is now awake, updating all data");
lastLocationChangeTimestamp = System.currentTimeMillis();
lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
requestAllData();
}
setActive();
}
// reset timestamp if elapsed and set inactive to false
if (isInactive && lastStateTimestamp + (API_SLEEP_INTERVAL_MINUTES * 60 * 1000) < System
.currentTimeMillis()) {
logger.debug("Vehicle did not fall asleep within sleep period, checking again");
setActive();
} else {
boolean wasInactive = isInactive;
isInactive = !isCharging() && !notReadyForSleep();
if (!wasInactive && isInactive) {
lastStateTimestamp = System.currentTimeMillis();
logger.debug("Vehicle is inactive");
}
}
break;
}
}
// secondly, reformat the response string to a JSON compliant
// object for some specific non-JSON compatible requests
switch (request) {
case MOBILE_ENABLED_STATE: {
jsonObject = new JsonObject();
jsonObject.addProperty(MOBILE_ENABLED_STATE, result);
break;
}
default: {
jsonObject = JsonParser.parseString(result).getAsJsonObject();
break;
}
}
}
// process the result
if (jsonObject != null && result != null && !"null".equals(result)) {
// deal with responses for "set" commands, which get confirmed
// positively, or negatively, in which case a reason for failure
// is provided
if (jsonObject.get("reason") != null && jsonObject.get("reason").getAsString() != null) {
boolean requestResult = jsonObject.get("result").getAsBoolean();
logger.debug("The request ({}) execution was {}, and reported '{}'", request,
requestResult ? "successful" : "not successful", jsonObject.get("reason").getAsString());
} else {
Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
long resultTimeStamp = 0;
for (Map.Entry<String, JsonElement> entry : entrySet) {
if ("timestamp".equals(entry.getKey())) {
resultTimeStamp = Long.parseLong(entry.getValue().getAsString());
if (logger.isTraceEnabled()) {
Date date = new Date(resultTimeStamp);
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
logger.trace("The request result timestamp is {}", dateFormatter.format(date));
}
break;
if (!wasInactive && isInactive) {
lastStateTimestamp = System.currentTimeMillis();
logger.debug("Vehicle is inactive");
}
}
} else if ("vehicleData".equals(request)) {
VehicleData vehicleData = gson.fromJson(result, VehicleData.class);
if (vehicleData == null) {
logger.error("Not able to parse response '{}'", result);
return;
}
driveState = vehicleData.drive_state;
if (Math.abs(lastLatitude - driveState.latitude) > locationThreshold
|| Math.abs(lastLongitude - driveState.longitude) > locationThreshold) {
logger.debug("Vehicle moved, resetting last location timestamp");
lastLatitude = driveState.latitude;
lastLongitude = driveState.longitude;
lastLocationChangeTimestamp = System.currentTimeMillis();
}
logger.trace("Drive state: {}", driveState.shift_state);
if ((driveState.shift_state == null) && (lastValidDriveStateNotNull)) {
logger.debug("Set NULL shiftstate time");
lastValidDriveStateNotNull = false;
lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
} else if (driveState.shift_state != null) {
logger.trace("Clear NULL shiftstate time");
lastValidDriveStateNotNull = true;
}
guiState = vehicleData.gui_settings;
vehicleState = vehicleData.vehicle_state;
chargeState = vehicleData.charge_state;
if (isCharging()) {
updateState(CHANNEL_CHARGE, OnOffType.ON);
} else {
updateState(CHANNEL_CHARGE, OnOffType.OFF);
}
climateState = vehicleData.climate_state;
BigDecimal avgtemp = roundBigDecimal(new BigDecimal(
(climateState.driver_temp_setting + climateState.passenger_temp_setting) / 2.0f));
updateState(CHANNEL_COMBINED_TEMP, new QuantityType<>(avgtemp, SIUnits.CELSIUS));
try {
lock.lock();
boolean proceed = true;
if (resultTimeStamp < lastTimeStamp && request == DRIVE_STATE) {
proceed = false;
}
Set<Map.Entry<String, JsonElement>> entrySet = new HashSet<>();
if (proceed) {
for (Map.Entry<String, JsonElement> entry : entrySet) {
try {
TeslaChannelSelector selector = TeslaChannelSelector
.getValueSelectorFromRESTID(entry.getKey());
if (!selector.isProperty()) {
if (!entry.getValue().isJsonNull()) {
updateState(selector.getChannelID(), teslaChannelSelectorProxy.getState(
entry.getValue().getAsString(), selector, editProperties()));
if (logger.isTraceEnabled()) {
logger.trace(
"The variable/value pair '{}':'{}' is successfully processed",
entry.getKey(), entry.getValue());
}
} else {
updateState(selector.getChannelID(), UnDefType.UNDEF);
}
} else if (!entry.getValue().isJsonNull()) {
Map<String, String> properties = editProperties();
properties.put(selector.getChannelID(), entry.getValue().getAsString());
updateProperties(properties);
entrySet.addAll(gson.toJsonTree(driveState, DriveState.class).getAsJsonObject().entrySet());
entrySet.addAll(gson.toJsonTree(guiState, GUIState.class).getAsJsonObject().entrySet());
entrySet.addAll(gson.toJsonTree(vehicleState, VehicleState.class).getAsJsonObject().entrySet());
entrySet.addAll(gson.toJsonTree(chargeState, ChargeState.class).getAsJsonObject().entrySet());
entrySet.addAll(gson.toJsonTree(climateState, ClimateState.class).getAsJsonObject().entrySet());
for (Map.Entry<String, JsonElement> entry : entrySet) {
try {
TeslaChannelSelector selector = TeslaChannelSelector
.getValueSelectorFromRESTID(entry.getKey());
if (!selector.isProperty()) {
if (!entry.getValue().isJsonNull()) {
updateState(selector.getChannelID(), teslaChannelSelectorProxy
.getState(entry.getValue().getAsString(), selector, editProperties()));
if (logger.isTraceEnabled()) {
logger.trace(
"The variable/value pair '{}':'{}' is successfully used to set property '{}'",
entry.getKey(), entry.getValue(), selector.getChannelID());
logger.trace("The variable/value pair '{}':'{}' is successfully processed",
entry.getKey(), entry.getValue());
}
} else {
updateState(selector.getChannelID(), UnDefType.UNDEF);
}
} else if (!entry.getValue().isJsonNull()) {
Map<String, String> properties = editProperties();
properties.put(selector.getChannelID(), entry.getValue().getAsString());
updateProperties(properties);
if (logger.isTraceEnabled()) {
logger.trace(
"The variable/value pair '{}':'{}' is successfully used to set property '{}'",
entry.getKey(), entry.getValue(), selector.getChannelID());
}
} catch (IllegalArgumentException e) {
logger.trace("The variable/value pair '{}':'{}' is not (yet) supported",
entry.getKey(), entry.getValue());
} catch (ClassCastException | IllegalStateException e) {
logger.trace("An exception occurred while converting the JSON data : '{}'",
e.getMessage(), e);
}
} catch (IllegalArgumentException e) {
logger.trace("The variable/value pair '{}':'{}' is not (yet) supported", entry.getKey(),
entry.getValue());
} catch (ClassCastException | IllegalStateException e) {
logger.trace("An exception occurred while converting the JSON data : '{}'",
e.getMessage(), e);
}
} else {
logger.warn("The result for request '{}' is discarded due to an out of sync timestamp",
request);
}
} finally {
lock.unlock();
@ -1069,40 +987,22 @@ public class TeslaVehicleHandler extends BaseThingHandler {
return value.setScale(1, RoundingMode.HALF_EVEN);
}
protected Runnable slowStateRunnable = () -> {
protected Runnable stateRunnable = () -> {
try {
queryVehicleAndUpdate();
boolean allowQuery = allowQuery();
if (allowQuery) {
requestData(CHARGE_STATE);
requestData(CLIMATE_STATE);
requestData(GUI_STATE);
queryVehicle(MOBILE_ENABLED_STATE);
requestAllData();
} else if (allowWakeUp) {
wakeUp();
} else if (isAwake()) {
logger.debug("slowpoll: Throttled to allow sleep, occupied/idle, or in a console mode");
logger.debug("Throttled state polling to allow sleep, occupied/idle, or in a console mode");
} else {
lastAdvModesTimestamp = System.currentTimeMillis();
}
} catch (Exception e) {
logger.warn("Exception occurred in slowStateRunnable", e);
}
};
protected Runnable fastStateRunnable = () -> {
if (getThing().getStatus() == ThingStatus.ONLINE) {
boolean allowQuery = allowQuery();
if (allowQuery) {
requestData(DRIVE_STATE);
requestData(VEHICLE_STATE);
} else if (allowWakeUp) {
wakeUp();
} else if (isAwake()) {
logger.debug("fastpoll: Throttled to allow sleep, occupied/idle, or in a console mode");
}
logger.warn("Exception occurred in stateRunnable", e);
}
};

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2023 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.tesla.internal.protocol;
/**
* The {@link VehicleData} is a data structure to capture
* variables sent by the Tesla API about a vehicle.
*
* @author Kai Kreuzer - Initial contribution
*/
public class VehicleData {
public String color;
public String display_name;
public String id;
public String option_codes;
public String vehicle_id;
public String vin;
public String tokens[];
public String state;
public boolean remote_start_enabled;
public boolean calendar_enabled;
public boolean notifications_enabled;
public String backseat_token;
public String backseat_token_updated_at;
public ChargeState charge_state;
public ClimateState climate_state;
public DriveState drive_state;
public GUIState gui_settings;
public VehicleConfig vehicle_config;
public VehicleState vehicle_state;
VehicleData() {
}
}