]> git.basschouten.com Git - openhab-addons.git/commitdiff
[tesla] Adapt binding to changed API from Tesla backend (#14922)
authorKai Kreuzer <kai@openhab.org>
Tue, 2 May 2023 19:49:06 +0000 (21:49 +0200)
committerGitHub <noreply@github.com>
Tue, 2 May 2023 19:49:06 +0000 (21:49 +0200)
* Adapt binding to changed API from Tesla backend

Signed-off-by: Kai Kreuzer <kai@openhab.org>
bundles/org.openhab.binding.tesla/README.md
bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaBindingConstants.java
bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaChannelSelectorProxy.java
bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaAccountHandler.java
bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaVehicleHandler.java
bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleData.java [new file with mode: 0644]

index b59b9a29896e8831bceb322a434e5545eefe65e2..e05c62dcd0e688b331be3b165367bdfb06fa05dc 100644 (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"}
-
-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"}
-
-Switch              TeslaCharge                 {channel="model3:myaccount:mycar:charge"}
-Switch              TeslaChargeToMax            {channel="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"}
-
-Number              TeslaChargerVoltage         {channel="model3:myaccount:mycar:chargervoltage"}
-Number              TeslaChargerPower           {channel="model3:myaccount:mycar:chargerpower"}
-Number              TeslaChargerCurrent         {channel="model3:myaccount:mycar:chargercurrent"}
-
-DateTime            TeslaScheduledChargingStart {channel="model3:myaccount:mycar:scheduledchargingstart"}
-Dimmer              TeslaSoC                    {channel="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              TeslaWakeup                 {channel="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              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              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"}
+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="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="account:model3:myaccount:mycar:charge"}
+Switch              TeslaChargeToMax            {channel="account:model3:myaccount:mycar:chargetomax"}
+
+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="account:model3:myaccount:mycar:chargervoltage"}
+Number              TeslaChargerPower           {channel="account:model3:myaccount:mycar:chargerpower"}
+Number              TeslaChargerCurrent         {channel="account:model3:myaccount:mycar:chargercurrent"}
+
+DateTime            TeslaScheduledChargingStart {channel="account:model3:myaccount:mycar:scheduledchargingstart"}
+Dimmer              TeslaSoC                    {channel="account:model3:myaccount:mycar:soc"}
+
+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="account:model3:myaccount:mycar:wakeup"}
+
+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="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="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
         }
     }
 }
index b1bbdc42b8acf9f23bb083d85644e2bd1eac8d12..5f175a88967a71d597c48a453434b19f4a89ca2c 100644 (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
index 87901488d6839ba073db13e4fcbcaa310fafc2da..58836d35c4f4a2f44b85363db2a1e1b54bbf418f 100644 (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)) {
index d4986bda5504d331e96fd6be2b4e2ac7b71f43e6..993ebf7eea971eeab443a31d1886abf850457fa8 100644 (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);
index 29e0e096bcebce9faf24df16a6884497090578a2..04bc9341e31afebbcbdd00e3fb38bbfcf79fe864 100644 (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;
-                            lastLocationChangeTimestamp = System.currentTimeMillis();
-                        }
-                        logger.trace("Drive state: {}", driveState.shift_state);
+                    if (vehicle != null && !lastState.equals(vehicle.state)) {
+                        lastState = vehicle.state;
 
-                        if ((driveState.shift_state == null) && (lastValidDriveStateNotNull)) {
-                            logger.debug("Set NULL shiftstate time");
-                            lastValidDriveStateNotNull = false;
+                        // 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();
-                        } else if (driveState.shift_state != null) {
-                            logger.trace("Clear NULL shiftstate time");
-                            lastValidDriveStateNotNull = true;
+                            requestAllData();
                         }
 
-                        break;
-                    }
-                    case GUI_STATE: {
-                        guiState = gson.fromJson(result, GUIState.class);
-                        break;
+                        setActive();
                     }
-                    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;
+                    // 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");
+                        }
                     }
-                    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;
+                } else if ("vehicleData".equals(request)) {
+                    VehicleData vehicleData = gson.fromJson(result, VehicleData.class);
+                    if (vehicleData == null) {
+                        logger.error("Not able to parse response '{}'", result);
+                        return;
                     }
-                    case "queryVehicle": {
-                        if (vehicle != null) {
-                            logger.debug("Vehicle state is {}", vehicle.state);
-                        } else {
-                            logger.debug("Vehicle state is initializing or unknown");
-                            break;
-                        }
-
-                        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");
-                            }
-                        }
+                    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");
 
-                        break;
+                        lastLatitude = driveState.latitude;
+                        lastLongitude = driveState.longitude;
+                        lastLocationChangeTimestamp = System.currentTimeMillis();
                     }
-                }
-
-                // 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;
+                    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;
                     }
-                }
-            }
 
-            // 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();
+                    guiState = vehicleData.gui_settings;
 
-                    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;
-                        }
+                    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;
-                        }
-
-                        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);
+                        Set<Map.Entry<String, JsonElement>> entrySet = new HashSet<>();
+
+                        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);
         }
     };
 
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 (file)
index 0000000..2295434
--- /dev/null
@@ -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() {
+    }
+}