diff --git a/bundles/org.openhab.binding.tesla/README.md b/bundles/org.openhab.binding.tesla/README.md index b59b9a298..e05c62dcd 100644 --- a/bundles/org.openhab.binding.tesla/README.md +++ b/bundles/org.openhab.binding.tesla/README.md @@ -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 } } } diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java index b1bbdc42b..5f175a889 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java @@ -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 diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java index 87901488d..58836d35c 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java @@ -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 properties) { if ("true".equals(s)) { diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java index d4986bda5..993ebf7ee 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java @@ -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); diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java index 29e0e096b..04bc9341e 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java @@ -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> entrySet = jsonObject.entrySet(); - - long resultTimeStamp = 0; - for (Map.Entry 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> entrySet = new HashSet<>(); - if (proceed) { - for (Map.Entry 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 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 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 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); } }; diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleData.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleData.java new file mode 100644 index 000000000..2295434cb --- /dev/null +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleData.java @@ -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() { + } +}