From bf2ce7b4186918c1e4389c7dcc13df9d5d2f7424 Mon Sep 17 00:00:00 2001 From: Kai Kreuzer Date: Wed, 3 May 2023 20:39:05 +0200 Subject: [PATCH] [tesla] Adapt binding to changed API from Tesla backend (#14924) * Adapt binding to changed API from Tesla backend * Add new translations and vehicle thing type Signed-off-by: Kai Kreuzer --- bundles/org.openhab.binding.tesla/README.md | 141 ++- .../tesla/internal/TeslaBindingConstants.java | 12 +- .../internal/TeslaChannelSelectorProxy.java | 236 +++-- .../tesla/internal/TeslaHandlerFactory.java | 13 +- .../TeslaVehicleDiscoveryService.java | 22 +- .../internal/handler/TeslaAccountHandler.java | 71 +- .../internal/handler/TeslaEventEndpoint.java | 22 +- .../internal/handler/TeslaSSOHandler.java | 4 +- .../internal/handler/TeslaVehicleHandler.java | 859 ++++++++---------- .../internal/handler/ThingWebClientUtil.java | 67 ++ .../internal/handler/VehicleListener.java | 6 +- .../internal/protocol/VehicleConfig.java | 19 + .../tesla/internal/protocol/VehicleData.java | 46 + .../internal/throttler/TimeProvider.java | 4 +- .../resources/OH-INF/i18n/tesla.properties | 52 +- .../main/resources/OH-INF/thing/channels.xml | 2 +- .../main/resources/OH-INF/thing/vehicle.xml | 67 ++ 17 files changed, 881 insertions(+), 762 deletions(-) create mode 100644 bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/ThingWebClientUtil.java create mode 100644 bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleData.java create mode 100644 bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/vehicle.xml diff --git a/bundles/org.openhab.binding.tesla/README.md b/bundles/org.openhab.binding.tesla/README.md index 85ba3310965..e05c62dcd0e 100644 --- a/bundles/org.openhab.binding.tesla/README.md +++ b/bundles/org.openhab.binding.tesla/README.md @@ -22,10 +22,11 @@ Access is established through a Tesla account as a bridge. The account cannot be automatically discovered, but has to be created manually. -Once an account is configured, it is automatically queried for associated vehicles and an Inbox entry is created for each of them. - -Note: Vehicles that are asleep might not be discovered, so you might want to wake it up through the Tesla app first. +Once an account is configured, it is queried for associated vehicles and an Inbox entry is created for each of them. +Note: Vehicles that are asleep are discovered and put into the Inbox, but their model cannot be determined. +As an effect, their channels are missing until the vehicle wakes up and can be fully queried. +A vehicle can be manually woken up by opening the Tesla app and checking the vehicle status in there. ## Bridge Configuration @@ -40,7 +41,6 @@ Please note that we in general consider it dangerous to enter your credentials i When using one of such apps, simply copy and paste the received refresh token into the account configuration. - ## Thing Configuration Parameters The vehicle Thing requires the vehicle's VIN as a configuration parameter `vin`. @@ -52,8 +52,7 @@ Additionally, the follow optional parameters may be defined. | valetpin | Valet PIN | false | PIN to use when enabling Valet Mode | | allowWakeupForCommands | Allow Wake-Up For Commands | false | Wake up the vehicle to send commands. May cause vehicle to stay awake | - -For further flexibility and experimentation, the following advanced parameters may also be set. +For further flexibility and experimentation, the following advanced parameters may also be set. | Parameter Name | Label | Default Value | Description | |-----------------------------|------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------| @@ -64,13 +63,12 @@ For further flexibility and experimentation, the following advanced parameters m | useAdvancedStatesForPolling | Use Console Modes and Occupancy for Inactivity | false | Use these states to help continue the fast polling of the API | `allowWakeup` should be used with caution as this determines whether openHAB is allowed to wake up the vehicle in order to retrieve data from it. -This setting is not recommended as it will result in a significant vampire drain (i.e. energy consumption although the vehicle is parking). +This setting is not recommended as it will result in a significant vampire drain (i.e. energy consumption although the vehicle is parking). `enableEvents` captures and processes data in near real-time for key variables by enabling events streamed by the Tesla back-end system. `inactivity` setting is ignored and will always be five minutes if homelink is available (car is at home) - ## Channels All vehicles support a huge number of channels - the following list shows the standard ones: @@ -88,7 +86,6 @@ All vehicles support a huge number of channels - the following list shows the st | odometer | Number:Length | Odometer | Odometer of the vehicle | | speed | Number:Speed | Speed | Vehicle speed | - Additionally, these advanced channels are available (not all are available on all vehicle types, e.g., the sunroof): | Channel ID | Item Type | Label | Description | @@ -100,7 +97,7 @@ Additionally, these advanced channels are available (not all are available on al | batteryheaternopower | Switch | Battery Heater Power | Indicates if there is enough power to use the battery heater | | batteryrange | Number:Length | Battery Range | Range of the battery | | calendarenabled | Switch | Calendar Enabled | Indicates if access to a remote calendar is enabled | -| centerdisplay | Number | Central Display State | Indicates the state of the central display in the vehicle, see [here](https://tesla-api.timdorr.com/vehicle/state/vehiclestate) for valid values | +| centerdisplay | Number | Central Display State | Indicates the state of the central display in the vehicle, see [here](https://tesla-api.timdorr.com/vehicle/state/vehiclestate) for valid values | | centerrearseatheater | Switch | Center Rear Seat Heater | Indicates if the center rear seat heater is switched on | | charge | Switch | Charge | Start (ON) or stop (OFF) charging | | chargecable | String | Charge Cable | Undocumented / To be defined | @@ -195,13 +192,11 @@ Additionally, these advanced channels are available (not all are available on al | wakeup | Switch | Wake Up | Wake up the vehicle from a (deep) sleep | | wiperbladeheater | Switch | Wiperblade Heater | Indicates if the wiperblade heater is switched on | - - ## Example demo.Things: -``` +```java Bridge tesla:account:myaccount "My Tesla Account" [ refreshToken="xxxx" ] { model3 mycar "My favorite car" [ vin="5YJSA7H25FFP53736"] } @@ -209,70 +204,70 @@ Bridge tesla:account:myaccount "My Tesla Account" [ refreshToken="xxxx" ] { demo.items: -``` -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"} +```java +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: -``` +```perl sitemap main label="Main" { Text item=TeslaUsableBatteryLevel label="Car" icon="tesla" valuecolor=[<=20="red",>60="green"] @@ -342,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 } } } @@ -366,7 +345,7 @@ sitemap main label="Main" demo.rule (for graphing online status in sitemap above) -``` +```java rule "Tesla State Changed" when Item TeslaState changed 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 f75638e0cc8..42e41f86879 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,19 +71,11 @@ 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 public static final ThingTypeUID THING_TYPE_ACCOUNT = new ThingTypeUID(BINDING_ID, "account"); + public static final ThingTypeUID THING_TYPE_VEHICLE = new ThingTypeUID(BINDING_ID, "vehicle"); public static final ThingTypeUID THING_TYPE_MODELS = new ThingTypeUID(BINDING_ID, "models"); public static final ThingTypeUID THING_TYPE_MODEL3 = new ThingTypeUID(BINDING_ID, "model3"); public static final ThingTypeUID THING_TYPE_MODELX = new ThingTypeUID(BINDING_ID, "modelx"); 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 7d76154d4be..5b7ba7c174b 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 @@ -48,10 +48,10 @@ public class TeslaChannelSelectorProxy { AUTO_COND("is_auto_conditioning_on", "autoconditioning", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -64,10 +64,10 @@ public class TeslaChannelSelectorProxy { BATTERY_HEATER("battery_heater_on", "batteryheater", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -76,10 +76,10 @@ public class TeslaChannelSelectorProxy { BATTERY_HEATER_NO_POWER("battery_heater_no_power", "batteryheaternopower", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -97,10 +97,10 @@ public class TeslaChannelSelectorProxy { CALENDAR_SUPPORTED("calendar_supported", "calendarsupported", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -109,10 +109,10 @@ public class TeslaChannelSelectorProxy { CALENDAR_ENABLED("calendar_enabled", "calendarenabled", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -123,10 +123,10 @@ public class TeslaChannelSelectorProxy { CHARGE(null, "charge", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -138,10 +138,10 @@ public class TeslaChannelSelectorProxy { CHARGE_ENABLE_REQUEST("charge_enable_request", "chargeenablerequest", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -184,10 +184,10 @@ public class TeslaChannelSelectorProxy { CHARGE_TO_MAX("charge_to_max_range", "chargetomax", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -197,10 +197,10 @@ public class TeslaChannelSelectorProxy { CHARGEPORT("charge_port_door_open", "chargeport", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -214,10 +214,10 @@ public class TeslaChannelSelectorProxy { CLIMATE_ON("is_climate_on", "climate", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -229,10 +229,10 @@ public class TeslaChannelSelectorProxy { DARK_RIMS("dark_rims", "darkrims", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -242,10 +242,10 @@ public class TeslaChannelSelectorProxy { DF("df", "driverfrontdoor", OpenClosedType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("OPEN"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("CLOSED"); } return super.getState(s); @@ -254,10 +254,10 @@ public class TeslaChannelSelectorProxy { DOOR_LOCK("locked", "doorlock", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -266,10 +266,10 @@ public class TeslaChannelSelectorProxy { DR("dr", "driverreardoor", OpenClosedType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("OPEN"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("CLOSED"); } return super.getState(s); @@ -319,10 +319,10 @@ public class TeslaChannelSelectorProxy { EU_VEHICLE("eu_vehicle", "european", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -332,10 +332,10 @@ public class TeslaChannelSelectorProxy { FAST_CHARGER("fast_charger_present", "fastcharger", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -346,10 +346,10 @@ public class TeslaChannelSelectorProxy { FLASH(null, "flashlights", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -358,10 +358,10 @@ public class TeslaChannelSelectorProxy { FRONT_DEFROSTER("is_front_defroster_on", "frontdefroster", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -370,10 +370,10 @@ public class TeslaChannelSelectorProxy { FT("ft", "fronttrunk", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -397,10 +397,10 @@ public class TeslaChannelSelectorProxy { HAS_SPOILER("has_spoiler", "spoiler", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -417,10 +417,10 @@ public class TeslaChannelSelectorProxy { HONK_HORN(null, "honkhorn", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -429,10 +429,10 @@ public class TeslaChannelSelectorProxy { HOMELINK_NEARBY("homelink_nearby", "homelink", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -491,10 +491,10 @@ public class TeslaChannelSelectorProxy { MANAGED_CHARGING("managed_charging_active", "managedcharging", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true")) { + if ("true".equals(s)) { return super.getState("ON"); } - if (s.equals("false")) { + if ("false".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -504,23 +504,23 @@ public class TeslaChannelSelectorProxy { false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true")) { + if ("true".equals(s)) { return super.getState("ON"); } - if (s.equals("false")) { + if ("false".equals(s)) { return super.getState("OFF"); } return super.getState(s); } }, 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 (s.equals("true")) { + if ("true".equals(s)) { return super.getState("ON"); } - if (s.equals("false")) { + if ("false".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -529,10 +529,10 @@ public class TeslaChannelSelectorProxy { MOTORIZED_CHARGE_PORT("motorized_charge_port", "motorizedchargeport", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -555,10 +555,10 @@ public class TeslaChannelSelectorProxy { NATIVE_LOCATION_SUPPORTED("native_location_supported", "nativelocationsupported", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -568,10 +568,10 @@ public class TeslaChannelSelectorProxy { NOT_ENOUGH_POWER_TO_HEAT("not_enough_power_to_heat", "notenoughpower", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -580,10 +580,10 @@ public class TeslaChannelSelectorProxy { NOTIFICATIONS_ENABLED("notifications_enabled", "notificationsenabled", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -592,10 +592,10 @@ public class TeslaChannelSelectorProxy { NOTIFICATIONS_SUPPORTED("notifications_supported", "notificationssupported", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -612,10 +612,10 @@ public class TeslaChannelSelectorProxy { OPEN_FRUNK(null, "openfrunk", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -624,10 +624,10 @@ public class TeslaChannelSelectorProxy { OPEN_TRUNK(null, "opentrunk", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -645,10 +645,10 @@ public class TeslaChannelSelectorProxy { PARSED_CALENDAR("parsed_calendar_supported", "parsedcalendar", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -666,10 +666,10 @@ public class TeslaChannelSelectorProxy { PF("pf", "passengerfrontdoor", OpenClosedType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("OPEN"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("CLOSED"); } return super.getState(s); @@ -679,10 +679,10 @@ public class TeslaChannelSelectorProxy { PR("pr", "passengerreardoor", OpenClosedType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("OPEN"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("CLOSED"); } return super.getState(s); @@ -691,10 +691,10 @@ public class TeslaChannelSelectorProxy { PRECONDITIONING("is_preconditioning", "preconditioning", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -705,7 +705,7 @@ public class TeslaChannelSelectorProxy { public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { State someState = super.getState(s); BigDecimal value = ((DecimalType) someState).toBigDecimal(); - if (properties.containsKey("distanceunits") && properties.get("distanceunits").equals("km/hr")) { + if (properties.containsKey("distanceunits") && "km/hr".equals(properties.get("distanceunits"))) { return new QuantityType<>(value, MetricPrefix.KILO(SIUnits.METRE)); } else { return new QuantityType<>(value, ImperialUnits.MILE); @@ -715,10 +715,10 @@ public class TeslaChannelSelectorProxy { REAR_DEFROSTER("is_rear_defroster_on", "reardefroster", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -727,10 +727,10 @@ public class TeslaChannelSelectorProxy { REAR_SEAT_HEATERS("rear_seat_heaters", "rearseatheaters", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -739,10 +739,10 @@ public class TeslaChannelSelectorProxy { REMOTE_START("remote_start", "remotestart", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -751,10 +751,10 @@ public class TeslaChannelSelectorProxy { REMOTE_START_ENABLED("remote_start_enabled", "remotestartenabled", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -763,10 +763,10 @@ public class TeslaChannelSelectorProxy { REMOTE_START_SUPPORTED("remote_start_supported", "remotestartsupported", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -775,10 +775,10 @@ public class TeslaChannelSelectorProxy { RESET_VALET_PIN(null, "resetvaletpin", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -787,10 +787,10 @@ public class TeslaChannelSelectorProxy { RHD("rhd", "rhd", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -801,10 +801,10 @@ public class TeslaChannelSelectorProxy { RT("rt", "reartrunk", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -821,10 +821,10 @@ public class TeslaChannelSelectorProxy { SCHEDULED_CHARGING_PENDING("scheduled_charging_pending", "scheduledchargingpending", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -844,10 +844,10 @@ public class TeslaChannelSelectorProxy { SENTRY_MODE("sentry_mode", "sentrymode", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -856,10 +856,10 @@ public class TeslaChannelSelectorProxy { SENTRY_MODE_AVAILABLE("sentry_mode_available", "sentrymodeavailable", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -869,10 +869,10 @@ public class TeslaChannelSelectorProxy { SIDEMIRROR_HEATING("side_mirror_heaters", "sidemirrorheaters", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -881,10 +881,10 @@ public class TeslaChannelSelectorProxy { SMART_PRECONDITIONING("smart_preconditioning", "smartpreconditioning", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -907,10 +907,10 @@ public class TeslaChannelSelectorProxy { STEERINGWHEEL_HEATER("steering_wheel_heater", "steeringwheelheater", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -919,10 +919,10 @@ public class TeslaChannelSelectorProxy { SUN_ROOF_PRESENT("sun_roof_installed", "sunroofinstalled", OnOffType.class, true) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -942,7 +942,7 @@ public class TeslaChannelSelectorProxy { TIMESTAMP("timestamp", "eventstamp", DateTimeType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - Date date = new Date(Long.valueOf(s)); + Date date = new Date(Long.parseLong(s)); SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); return super.getState(dateFormatter.format(date)); } @@ -950,10 +950,10 @@ public class TeslaChannelSelectorProxy { TRIP_CARGING("trip_charging", "tripcharging", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -970,10 +970,10 @@ public class TeslaChannelSelectorProxy { VALET_MODE("valet_mode", "valetmode", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -982,10 +982,10 @@ public class TeslaChannelSelectorProxy { VALET_PIN("valet_pin_needed", "valetpin", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -995,10 +995,10 @@ public class TeslaChannelSelectorProxy { WAKEUP(null, "wakeup", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -1007,10 +1007,10 @@ public class TeslaChannelSelectorProxy { WIPERBLADE_HEATER("wiper_blade_heater", "wiperbladeheater", OnOffType.class, false) { @Override public State getState(String s, TeslaChannelSelectorProxy proxy, Map properties) { - if (s.equals("true") || s.equals("1")) { + if ("true".equals(s) || "1".equals(s)) { return super.getState("ON"); } - if (s.equals("false") || s.equals("0")) { + if ("false".equals(s) || "0".equals(s)) { return super.getState("OFF"); } return super.getState(s); @@ -1059,10 +1059,8 @@ public class TeslaChannelSelectorProxy { if (state != null) { return state; } - } catch (NoSuchMethodException e) { - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } catch (InvocationTargetException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { } return null; diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaHandlerFactory.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaHandlerFactory.java index d89fea388cd..613cad0d007 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaHandlerFactory.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/TeslaHandlerFactory.java @@ -27,6 +27,7 @@ import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.WebSocketFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeMigrationService; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; @@ -50,21 +51,24 @@ public class TeslaHandlerFactory extends BaseThingHandlerFactory { private static final int EVENT_STREAM_CONNECT_TIMEOUT = 3; private static final int EVENT_STREAM_READ_TIMEOUT = 200; - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_MODELS, - THING_TYPE_MODEL3, THING_TYPE_MODELX, THING_TYPE_MODELY); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCOUNT, THING_TYPE_VEHICLE, + THING_TYPE_MODELS, THING_TYPE_MODEL3, THING_TYPE_MODELX, THING_TYPE_MODELY); private final ClientBuilder clientBuilder; private final HttpClientFactory httpClientFactory; private final WebSocketFactory webSocketFactory; + private final ThingTypeMigrationService thingTypeMigrationService; @Activate public TeslaHandlerFactory(@Reference ClientBuilder clientBuilder, @Reference HttpClientFactory httpClientFactory, - final @Reference WebSocketFactory webSocketFactory) { + final @Reference WebSocketFactory webSocketFactory, + final @Reference ThingTypeMigrationService thingTypeMigrationService) { this.clientBuilder = clientBuilder // .connectTimeout(EVENT_STREAM_CONNECT_TIMEOUT, TimeUnit.SECONDS) .readTimeout(EVENT_STREAM_READ_TIMEOUT, TimeUnit.SECONDS); this.httpClientFactory = httpClientFactory; this.webSocketFactory = webSocketFactory; + this.thingTypeMigrationService = thingTypeMigrationService; } @Override @@ -77,7 +81,8 @@ public class TeslaHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) { - return new TeslaAccountHandler((Bridge) thing, clientBuilder.build(), httpClientFactory); + return new TeslaAccountHandler((Bridge) thing, clientBuilder.build(), httpClientFactory, + thingTypeMigrationService); } else { return new TeslaVehicleHandler(thing, webSocketFactory); } diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/discovery/TeslaVehicleDiscoveryService.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/discovery/TeslaVehicleDiscoveryService.java index 1154c6d6208..728c4114ce0 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/discovery/TeslaVehicleDiscoveryService.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/discovery/TeslaVehicleDiscoveryService.java @@ -81,8 +81,10 @@ public class TeslaVehicleDiscoveryService extends AbstractDiscoveryService @Override public void vehicleFound(Vehicle vehicle, VehicleConfig vehicleConfig) { - ThingTypeUID type = identifyModel(vehicleConfig); + ThingTypeUID type = vehicleConfig == null ? TeslaBindingConstants.THING_TYPE_VEHICLE + : vehicleConfig.identifyModel(); if (type != null) { + logger.debug("Found a {} vehicle", type.getId()); ThingUID thingUID = new ThingUID(type, handler.getThing().getUID(), vehicle.vin); DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withLabel(vehicle.display_name) .withBridge(handler.getThing().getUID()).withProperty(TeslaBindingConstants.VIN, vehicle.vin) @@ -90,22 +92,4 @@ public class TeslaVehicleDiscoveryService extends AbstractDiscoveryService thingDiscovered(discoveryResult); } } - - private ThingTypeUID identifyModel(VehicleConfig vehicleConfig) { - logger.debug("Found a {} vehicle", vehicleConfig.car_type); - switch (vehicleConfig.car_type) { - case "models": - case "models2": - return TeslaBindingConstants.THING_TYPE_MODELS; - case "modelx": - return TeslaBindingConstants.THING_TYPE_MODELX; - case "model3": - return TeslaBindingConstants.THING_TYPE_MODEL3; - case "modely": - return TeslaBindingConstants.THING_TYPE_MODELY; - default: - logger.debug("Found unknown vehicle type '{}' - ignoring it.", vehicleConfig.car_type); - return null; - } - } } 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 9b01db6869e..8bfcb797e44 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 @@ -32,9 +32,11 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +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; @@ -43,6 +45,7 @@ import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusInfo; +import org.openhab.core.thing.ThingTypeMigrationService; import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; @@ -66,7 +69,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler { public static final int API_MAXIMUM_ERRORS_IN_INTERVAL = 3; public static final int API_ERROR_INTERVAL_SECONDS = 15; private static final int CONNECT_RETRY_INTERVAL = 15000; - private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") .withZone(ZoneId.systemDefault()); private final Logger logger = LoggerFactory.getLogger(TeslaAccountHandler.class); @@ -80,6 +83,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler { final WebTarget wakeUpTarget; private final TeslaSSOHandler ssoHandler; + private final ThingTypeMigrationService thingTypeMigrationService; // Threading and Job related variables protected ScheduledFuture connectJob; @@ -96,10 +100,12 @@ public class TeslaAccountHandler extends BaseBridgeHandler { private TokenResponse logonToken; private final Set vehicleListeners = new HashSet<>(); - public TeslaAccountHandler(Bridge bridge, Client teslaClient, HttpClientFactory httpClientFactory) { + public TeslaAccountHandler(Bridge bridge, Client teslaClient, HttpClientFactory httpClientFactory, + ThingTypeMigrationService thingTypeMigrationService) { super(bridge); this.teslaTarget = teslaClient.target(URI_OWNERS); this.ssoHandler = new TeslaSSOHandler(httpClientFactory.getCommonHttpClient()); + this.thingTypeMigrationService = thingTypeMigrationService; this.vehiclesTarget = teslaTarget.path(API_VERSION).path(VEHICLES); this.vehicleTarget = vehiclesTarget.path(PATH_VEHICLE_ID); @@ -143,7 +149,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler { } public void scanForVehicles() { - scheduler.execute(() -> queryVehicles()); + scheduler.execute(this::queryVehicles); } public void addVehicleListener(VehicleListener listener) { @@ -179,7 +185,6 @@ public class TeslaAccountHandler extends BaseBridgeHandler { ThingStatusInfo authenticationResult = authenticate(); updateStatus(authenticationResult.getStatus(), authenticationResult.getStatusDetail(), authenticationResult.getDescription()); - return false; } else { apiIntervalErrors++; if (immediatelyFail || apiIntervalErrors >= API_MAXIMUM_ERRORS_IN_INTERVAL) { @@ -221,11 +226,11 @@ 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); - if (responseString == null || responseString.isBlank()) { - continue; + String responseString = invokeAndParse(vehicle.id, null, null, dataRequestTarget, 0); + VehicleConfig vehicleConfig = null; + if (responseString != null && !responseString.isBlank()) { + vehicleConfig = gson.fromJson(responseString, VehicleData.class).vehicle_config; } - VehicleConfig vehicleConfig = gson.fromJson(responseString, VehicleConfig.class); for (VehicleListener listener : vehicleListeners) { listener.vehicleFound(vehicle, vehicleConfig); } @@ -233,6 +238,14 @@ public class TeslaAccountHandler extends BaseBridgeHandler { if (vehicle.vin.equals(vehicleThing.getConfiguration().get(VIN))) { TeslaVehicleHandler vehicleHandler = (TeslaVehicleHandler) vehicleThing.getHandler(); if (vehicleHandler != null) { + if (TeslaBindingConstants.THING_TYPE_VEHICLE.equals(vehicleThing.getThingTypeUID()) + && vehicleConfig != null) { + // Seems the type of this vehicle has not been identified before, so let's switch the + // thing type of it + thingTypeMigrationService.migrateThingType(vehicleThing, vehicleConfig.identifyModel(), + vehicleThing.getConfiguration()); + break; + } logger.debug("Querying the vehicle: VIN {}", vehicle.vin); String vehicleJSON = gson.toJson(vehicle); vehicleHandler.parseAndUpdate("queryVehicle", null, vehicleJSON); @@ -256,13 +269,13 @@ public class TeslaAccountHandler extends BaseBridgeHandler { TokenResponse token = logonToken; boolean hasExpired = true; - logger.debug("Current authentication time {}", dateFormatter.format(Instant.now())); + logger.debug("Current authentication time {}", DATE_FORMATTER.format(Instant.now())); if (token != null) { Instant tokenCreationInstant = Instant.ofEpochMilli(token.created_at * 1000); Instant tokenExpiresInstant = Instant.ofEpochMilli((token.created_at + token.expires_in) * 1000); - logger.debug("Found a request token from {}", dateFormatter.format(tokenCreationInstant)); - logger.debug("Access token expiration time {}", dateFormatter.format(tokenExpiresInstant)); + logger.debug("Found a request token from {}", DATE_FORMATTER.format(tokenCreationInstant)); + logger.debug("Access token expiration time {}", DATE_FORMATTER.format(tokenExpiresInstant)); if (tokenExpiresInstant.isBefore(Instant.now())) { logger.debug("The access token has expired"); @@ -308,15 +321,13 @@ public class TeslaAccountHandler extends BaseBridgeHandler { .header("Authorization", "Bearer " + logonToken.access_token) .post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE)); } + } else if (command != null) { + response = target.resolveTemplate("cmd", command).resolveTemplate("vid", vehicleId) + .request(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", "Bearer " + logonToken.access_token).get(); } else { - if (command != null) { - response = target.resolveTemplate("cmd", command).resolveTemplate("vid", vehicleId) - .request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + logonToken.access_token).get(); - } else { - response = target.resolveTemplate("vid", vehicleId).request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", "Bearer " + logonToken.access_token).get(); - } + response = target.resolveTemplate("vid", vehicleId).request(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", "Bearer " + logonToken.access_token).get(); } if (!checkResponse(response, false)) { @@ -330,7 +341,6 @@ public class TeslaAccountHandler extends BaseBridgeHandler { logger.debug("Retrying to send the command {}.", command); return invokeAndParse(vehicleId, command, payLoad, target, noOfretries - 1); } catch (InterruptedException e) { - return null; } } return null; @@ -353,8 +363,9 @@ public class TeslaAccountHandler extends BaseBridgeHandler { lock.lock(); ThingStatusInfo status = getThing().getStatusInfo(); - if (status.getStatus() != ThingStatus.ONLINE - && status.getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) { + if ((status.getStatus() != ThingStatus.ONLINE + && status.getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR) + || hasUnidentifiedVehicles()) { logger.debug("Setting up an authenticated connection to the Tesla back-end"); ThingStatusInfo authenticationResult = authenticate(); @@ -394,19 +405,16 @@ public class TeslaAccountHandler extends BaseBridgeHandler { } } } - } else { - if (response != null) { - logger.error("Error fetching the list of vehicles : {}:{}", response.getStatus(), - response.getStatusInfo()); - updateStatus(ThingStatus.OFFLINE); - } + } else if (response != null) { + logger.error("Error fetching the list of vehicles : {}:{}", response.getStatus(), + response.getStatusInfo()); + updateStatus(ThingStatus.OFFLINE); } } else if (authenticationResult.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR) { // make sure to set thing to CONFIGURATION_ERROR in case of failed authentication in order not to // hit request limit on retries on the Tesla SSO endpoints. updateStatus(ThingStatus.OFFLINE, authenticationResult.getStatusDetail()); } - } } catch (Exception e) { logger.error("An exception occurred while connecting to the Tesla back-end: '{}'", e.getMessage(), e); @@ -415,6 +423,11 @@ public class TeslaAccountHandler extends BaseBridgeHandler { } }; + private boolean hasUnidentifiedVehicles() { + return getThing().getThings().stream() + .anyMatch(vehicle -> TeslaBindingConstants.THING_TYPE_VEHICLE.equals(vehicle.getThingTypeUID())); + } + protected class Request implements Runnable { private static final int NO_OF_RETRIES = 3; diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaEventEndpoint.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaEventEndpoint.java index 1eb6cfea2ac..d5ccadb9383 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaEventEndpoint.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaEventEndpoint.java @@ -22,7 +22,6 @@ import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.websocket.api.Session; @@ -32,6 +31,7 @@ import org.eclipse.jetty.websocket.api.WebSocketPingPongListener; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.openhab.binding.tesla.internal.protocol.Event; import org.openhab.core.io.net.http.WebSocketFactory; +import org.openhab.core.thing.ThingUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,7 +54,6 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL private String endpointId; protected WebSocketFactory webSocketFactory; - private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger(); private WebSocketClient client; private ConnectionState connectionState = ConnectionState.CLOSED; @@ -62,11 +61,12 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL private EventHandler eventHandler; private final Gson gson = new Gson(); - public TeslaEventEndpoint(WebSocketFactory webSocketFactory) { + public TeslaEventEndpoint(ThingUID uid, WebSocketFactory webSocketFactory) { try { - this.endpointId = "TeslaEventEndpoint-" + INSTANCE_COUNTER.incrementAndGet(); + this.endpointId = "TeslaEventEndpoint-" + uid.getAsString(); - client = webSocketFactory.createWebSocketClient(endpointId); + String name = ThingWebClientUtil.buildWebClientConsumerName(uid, null); + client = webSocketFactory.createWebSocketClient(name); this.client.setConnectTimeout(TIMEOUT_MILLISECONDS); this.client.setMaxIdleTimeout(IDLE_TIMEOUT_MILLISECONDS); } catch (Exception e) { @@ -74,6 +74,16 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL } } + public void close() { + try { + if (client.isRunning()) { + client.stop(); + } + } catch (Exception e) { + logger.warn("An exception occurred while stopping the WebSocket client : {}", e.getMessage()); + } + } + public void connect(URI endpointURI) { if (connectionState == ConnectionState.CONNECTED) { return; @@ -113,7 +123,7 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL this.session = session; } - public void close() { + public void closeConnection() { try { connectionState = ConnectionState.CLOSING; if (session != null && session.isOpen()) { diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaSSOHandler.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaSSOHandler.java index d2bf5073e59..03b69b8872b 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaSSOHandler.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/TeslaSSOHandler.java @@ -46,7 +46,7 @@ public class TeslaSSOHandler { private final HttpClient httpClient; private final Gson gson = new Gson(); private final Logger logger = LoggerFactory.getLogger(TeslaSSOHandler.class); - private static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") .withZone(ZoneId.systemDefault()); public TeslaSSOHandler(HttpClient httpClient) { @@ -74,7 +74,7 @@ public class TeslaSSOHandler { if (tokenResponse != null && tokenResponse.access_token != null && !tokenResponse.access_token.isEmpty()) { tokenResponse.created_at = Instant.now().getEpochSecond(); - logger.debug("Access token expires in {} seconds at {}", tokenResponse.expires_in, dateFormatter + logger.debug("Access token expires in {} seconds at {}", tokenResponse.expires_in, DATE_FORMATTER .format(Instant.ofEpochMilli((tokenResponse.created_at + tokenResponse.expires_in) * 1000))); return tokenResponse; } else { 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 fff168e5051..f2f36eb5549 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(); @@ -168,7 +169,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { account = (TeslaAccountHandler) getBridge().getHandler(); lock = new ReentrantLock(); - scheduler.execute(() -> queryVehicleAndUpdate()); + scheduler.execute(this::queryVehicleAndUpdate); lock.lock(); try { @@ -181,23 +182,17 @@ 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); } if (enableEvents) { if (eventThread == null) { - eventThread = new Thread(eventRunnable, "openHAB-Tesla-Events-" + getThing().getUID()); + eventThread = new Thread(eventRunnable, "OH-binding-" + getThing().getUID() + "-events"); eventThread.start(); } } - } finally { lock.unlock(); } @@ -208,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()) { @@ -257,219 +247,214 @@ public class TeslaVehicleHandler extends BaseThingHandler { // Request the state of all known variables. This is sub-optimal, but the requests get scheduled and // throttled so we are safe not to break the Tesla SLA requestAllData(); - } else { - if (selector != null) { - if (!isAwake() && allowWakeUpForCommands) { - logger.debug("Waking vehicle to send command."); - wakeUp(); - setActive(); - } - try { - switch (selector) { - case CHARGE_LIMIT_SOC: { - if (command instanceof PercentType) { - setChargeLimit(((PercentType) command).intValue()); - } else if (command instanceof OnOffType && command == OnOffType.ON) { - setChargeLimit(100); - } else if (command instanceof OnOffType && command == OnOffType.OFF) { - setChargeLimit(0); - } else if (command instanceof IncreaseDecreaseType - && command == IncreaseDecreaseType.INCREASE) { - setChargeLimit(Math.min(chargeState.charge_limit_soc + 1, 100)); - } else if (command instanceof IncreaseDecreaseType - && command == IncreaseDecreaseType.DECREASE) { - setChargeLimit(Math.max(chargeState.charge_limit_soc - 1, 0)); - } - break; + } else if (selector != null) { + if (!isAwake() && allowWakeUpForCommands) { + logger.debug("Waking vehicle to send command."); + wakeUp(); + setActive(); + } + try { + switch (selector) { + case CHARGE_LIMIT_SOC: { + if (command instanceof PercentType) { + setChargeLimit(((PercentType) command).intValue()); + } else if (command instanceof OnOffType && command == OnOffType.ON) { + setChargeLimit(100); + } else if (command instanceof OnOffType && command == OnOffType.OFF) { + setChargeLimit(0); + } else if (command instanceof IncreaseDecreaseType + && command == IncreaseDecreaseType.INCREASE) { + setChargeLimit(Math.min(chargeState.charge_limit_soc + 1, 100)); + } else if (command instanceof IncreaseDecreaseType + && command == IncreaseDecreaseType.DECREASE) { + setChargeLimit(Math.max(chargeState.charge_limit_soc - 1, 0)); } - case CHARGE_AMPS: - Integer amps = null; - if (command instanceof DecimalType) { - amps = ((DecimalType) command).intValue(); - } - if (command instanceof QuantityType) { - QuantityType qamps = ((QuantityType) command).toUnit(Units.AMPERE); - if (qamps != null) { - amps = qamps.intValue(); - } - } - if (amps != null) { - if (amps < 5 || amps > 32) { - logger.warn("Charging amps can only be set in a range of 5-32A, but not to {}A.", - amps); - return; - } - setChargingAmps(amps); - } - break; - case COMBINED_TEMP: { - QuantityType quantity = commandToQuantityType(command); - if (quantity != null) { - setCombinedTemperature(quanityToRoundedFloat(quantity)); - } - break; - } - case DRIVER_TEMP: { - QuantityType quantity = commandToQuantityType(command); - if (quantity != null) { - setDriverTemperature(quanityToRoundedFloat(quantity)); - } - break; - } - case PASSENGER_TEMP: { - QuantityType quantity = commandToQuantityType(command); - if (quantity != null) { - setPassengerTemperature(quanityToRoundedFloat(quantity)); - } - break; - } - case SENTRY_MODE: { - if (command instanceof OnOffType) { - setSentryMode(command == OnOffType.ON); - } - break; - } - case SUN_ROOF_STATE: { - if (command instanceof StringType) { - setSunroof(command.toString()); - } - break; - } - case CHARGE_TO_MAX: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - setMaxRangeCharging(true); - } else { - setMaxRangeCharging(false); - } - } - break; - } - case CHARGE: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - charge(true); - } else { - charge(false); - } - } - break; - } - case FLASH: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - flashLights(); - } - } - break; - } - case HONK_HORN: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - honkHorn(); - } - } - break; - } - case CHARGEPORT: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - openChargePort(); - } - } - break; - } - case DOOR_LOCK: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - lockDoors(true); - } else { - lockDoors(false); - } - } - break; - } - case AUTO_COND: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - autoConditioning(true); - } else { - autoConditioning(false); - } - } - break; - } - case WAKEUP: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - wakeUp(); - } - } - break; - } - case FT: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - openFrunk(); - } - } - break; - } - case RT: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - if (vehicleState.rt == 0) { - openTrunk(); - } - } else { - if (vehicleState.rt == 1) { - closeTrunk(); - } - } - } - break; - } - case VALET_MODE: { - if (command instanceof OnOffType) { - int valetpin = ((BigDecimal) getConfig().get(VALETPIN)).intValue(); - if (((OnOffType) command) == OnOffType.ON) { - setValetMode(true, valetpin); - } else { - setValetMode(false, valetpin); - } - } - break; - } - case RESET_VALET_PIN: { - if (command instanceof OnOffType) { - if (((OnOffType) command) == OnOffType.ON) { - resetValetPin(); - } - } - break; - } - case STEERINGWHEEL_HEATER: { - if (command instanceof OnOffType) { - boolean commandBooleanValue = ((OnOffType) command) == OnOffType.ON ? true : false; - setSteeringWheelHeater(commandBooleanValue); - } - break; - } - default: - break; + break; } - return; - } catch (IllegalArgumentException e) { - logger.warn( - "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", - channelID, command.toString()); + case CHARGE_AMPS: + Integer amps = null; + if (command instanceof DecimalType) { + amps = ((DecimalType) command).intValue(); + } + if (command instanceof QuantityType) { + QuantityType qamps = ((QuantityType) command).toUnit(Units.AMPERE); + if (qamps != null) { + amps = qamps.intValue(); + } + } + if (amps != null) { + if (amps < 5 || amps > 32) { + logger.warn("Charging amps can only be set in a range of 5-32A, but not to {}A.", amps); + return; + } + setChargingAmps(amps); + } + break; + case COMBINED_TEMP: { + QuantityType quantity = commandToQuantityType(command); + if (quantity != null) { + setCombinedTemperature(quanityToRoundedFloat(quantity)); + } + break; + } + case DRIVER_TEMP: { + QuantityType quantity = commandToQuantityType(command); + if (quantity != null) { + setDriverTemperature(quanityToRoundedFloat(quantity)); + } + break; + } + case PASSENGER_TEMP: { + QuantityType quantity = commandToQuantityType(command); + if (quantity != null) { + setPassengerTemperature(quanityToRoundedFloat(quantity)); + } + break; + } + case SENTRY_MODE: { + if (command instanceof OnOffType) { + setSentryMode(command == OnOffType.ON); + } + break; + } + case SUN_ROOF_STATE: { + if (command instanceof StringType) { + setSunroof(command.toString()); + } + break; + } + case CHARGE_TO_MAX: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + setMaxRangeCharging(true); + } else { + setMaxRangeCharging(false); + } + } + break; + } + case CHARGE: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + charge(true); + } else { + charge(false); + } + } + break; + } + case FLASH: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + flashLights(); + } + } + break; + } + case HONK_HORN: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + honkHorn(); + } + } + break; + } + case CHARGEPORT: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + openChargePort(); + } + } + break; + } + case DOOR_LOCK: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + lockDoors(true); + } else { + lockDoors(false); + } + } + break; + } + case AUTO_COND: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + autoConditioning(true); + } else { + autoConditioning(false); + } + } + break; + } + case WAKEUP: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + wakeUp(); + } + } + break; + } + case FT: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + openFrunk(); + } + } + break; + } + case RT: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + if (vehicleState.rt == 0) { + openTrunk(); + } + } else if (vehicleState.rt == 1) { + closeTrunk(); + } + } + break; + } + case VALET_MODE: { + if (command instanceof OnOffType) { + int valetpin = ((BigDecimal) getConfig().get(VALETPIN)).intValue(); + if (((OnOffType) command) == OnOffType.ON) { + setValetMode(true, valetpin); + } else { + setValetMode(false, valetpin); + } + } + break; + } + case RESET_VALET_PIN: { + if (command instanceof OnOffType) { + if (((OnOffType) command) == OnOffType.ON) { + resetValetPin(); + } + } + break; + } + case STEERINGWHEEL_HEATER: { + if (command instanceof OnOffType) { + boolean commandBooleanValue = ((OnOffType) command) == OnOffType.ON ? true : false; + setSteeringWheelHeater(commandBooleanValue); + } + break; + } + default: + break; } + return; + } catch (IllegalArgumentException e) { + logger.warn( + "An error occurred while trying to set the read-only variable associated with channel '{}' to '{}'", + channelID, command.toString()); } } } public void sendCommand(String command, String payLoad, WebTarget target) { - if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) { + if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) { Request request = account.newRequest(this, command, payLoad, target, allowWakeUpForCommands); if (stateThrottler != null) { stateThrottler.submit(COMMAND_THROTTLE, request); @@ -482,7 +467,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { } public void sendCommand(String command, String payLoad) { - if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) { + if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) { Request request = account.newRequest(this, command, payLoad, account.commandTarget, allowWakeUpForCommands); if (stateThrottler != null) { stateThrottler.submit(COMMAND_THROTTLE, request); @@ -491,7 +476,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { } public void sendCommand(String command, WebTarget target) { - if (command.equals(COMMAND_WAKE_UP) || isAwake() || allowWakeUpForCommands) { + if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) { Request request = account.newRequest(this, command, "{}", target, allowWakeUpForCommands); if (stateThrottler != null) { stateThrottler.submit(COMMAND_THROTTLE, request); @@ -500,7 +485,8 @@ public class TeslaVehicleHandler extends BaseThingHandler { } public void requestData(String command, String payLoad) { - if (command.equals(COMMAND_WAKE_UP) || 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); @@ -533,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() { @@ -597,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); @@ -650,7 +632,6 @@ public class TeslaVehicleHandler extends BaseThingHandler { } else if (response != null && response.getStatus() == 401) { logger.debug("The access token has expired, trying to get a new one."); account.authenticate(); - return false; } else { apiIntervalErrors++; if (immediatelyFail || apiIntervalErrors >= TeslaAccountHandler.API_MAXIMUM_ERRORS_IN_INTERVAL) { @@ -678,29 +659,25 @@ 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) { - if (state.equals("vent") || state.equals("close")) { + if ("vent".equals(state) || "close".equals(state)) { 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); } @@ -721,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) { @@ -740,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() { @@ -761,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() { @@ -789,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() { @@ -818,31 +785,34 @@ public class TeslaVehicleHandler extends BaseThingHandler { if (authHeader != null) { try { // get a list of vehicles - Response response = account.vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE) - .header("Authorization", authHeader).get(); + synchronized (account.vehiclesTarget) { + Response response = account.vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", authHeader).get(); - logger.debug("Querying the vehicle, response : {}, {}", response.getStatus(), - response.getStatusInfo().getReasonPhrase()); + logger.debug("Querying the vehicle, response : {}, {}", response.getStatus(), + response.getStatusInfo().getReasonPhrase()); - if (!checkResponse(response, true)) { - logger.debug("An error occurred while querying the vehicle"); - return null; - } - - JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject(); - Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class); - - for (Vehicle vehicle : vehicleArray) { - logger.debug("Querying the vehicle: VIN {}", vehicle.vin); - if (vehicle.vin.equals(getConfig().get(VIN))) { - vehicleJSON = gson.toJson(vehicle); - parseAndUpdate("queryVehicle", null, vehicleJSON); - if (logger.isTraceEnabled()) { - logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicle_id, - vehicle.tokens); - } - return vehicle; + if (!checkResponse(response, true)) { + logger.debug("An error occurred while querying the vehicle"); + return null; } + + JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject(); + Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class); + + for (Vehicle vehicle : vehicleArray) { + logger.debug("Querying the vehicle: VIN {}", vehicle.vin); + if (vehicle.vin.equals(getConfig().get(VIN))) { + vehicleJSON = gson.toJson(vehicle); + parseAndUpdate("queryVehicle", null, vehicleJSON); + if (logger.isTraceEnabled()) { + logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicle_id, + vehicle.tokens); + } + return vehicle; + } + } + } } catch (ProcessingException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -856,198 +826,140 @@ public class TeslaVehicleHandler extends BaseThingHandler { } public void parseAndUpdate(String request, String payLoad, String result) { - final Double LOCATION_THRESHOLD = .0000001; - - JsonObject jsonObject = null; + final double locationThreshold = .0000001; 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) > LOCATION_THRESHOLD - || Math.abs(lastLongitude - driveState.longitude) > LOCATION_THRESHOLD) { - 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 '{}'", new Object[] { 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.valueOf(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); + 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 processed", + entry.getKey(), entry.getValue()); } } 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()); - } - } + 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(); @@ -1075,48 +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("Throttled state polling to allow sleep, occupied/idle, or in a console mode"); } else { - if (allowWakeUp) { - wakeUp(); - } else { - if (isAwake()) { - logger.debug("slowpoll: Throttled to allow sleep, occupied/idle, or in a console mode"); - } else { - lastAdvModesTimestamp = System.currentTimeMillis(); - } - } + 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); } }; @@ -1127,7 +1013,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { @Override public void run() { - eventEndpoint = new TeslaEventEndpoint(webSocketFactory); + eventEndpoint = new TeslaEventEndpoint(getThing().getUID(), webSocketFactory); eventEndpoint.addEventHandler(new TeslaEventEndpoint.EventHandler() { @Override public void handleEvent(Event event) { @@ -1140,7 +1026,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { logger.debug("Event : Received an update: '{}'", event.value); String vals[] = event.value.split(","); - long currentTimeStamp = Long.valueOf(vals[0]); + long currentTimeStamp = Long.parseLong(vals[0]); long systemTimeStamp = System.currentTimeMillis(); if (logger.isDebugEnabled()) { SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); @@ -1151,7 +1037,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { } if (systemTimeStamp - currentTimeStamp < EVENT_TIMESTAMP_AGE_LIMIT) { if (currentTimeStamp > lastTimeStamp) { - lastTimeStamp = Long.valueOf(vals[0]); + lastTimeStamp = Long.parseLong(vals[0]); if (logger.isDebugEnabled()) { SimpleDateFormat dateFormatter = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS"); @@ -1187,15 +1073,13 @@ public class TeslaVehicleHandler extends BaseThingHandler { } } } - } else { - if (logger.isDebugEnabled()) { - SimpleDateFormat dateFormatter = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSS"); - logger.debug( - "Event : Discarding an event with an out of sync timestamp {} (last is {})", - dateFormatter.format(new Date(currentTimeStamp)), - dateFormatter.format(new Date(lastTimeStamp))); - } + } else if (logger.isDebugEnabled()) { + SimpleDateFormat dateFormatter = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS"); + logger.debug( + "Event : Discarding an event with an out of sync timestamp {} (last is {})", + dateFormatter.format(new Date(currentTimeStamp)), + dateFormatter.format(new Date(lastTimeStamp))); } } else { if (logger.isDebugEnabled()) { @@ -1209,13 +1093,13 @@ public class TeslaVehicleHandler extends BaseThingHandler { } if (systemTimeStamp - currentTimeStamp > EVENT_TIMESTAMP_MAX_DELTA) { logger.trace("Event : The event endpoint will be reset"); - eventEndpoint.close(); + eventEndpoint.closeConnection(); } } break; case "data:error": logger.debug("Event : Received an error: '{}'/'{}'", event.value, event.error_type); - eventEndpoint.close(); + eventEndpoint.closeConnection(); break; } } @@ -1260,7 +1144,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { "Event : Reached the maximum number of errors ({}) for the current interval ({} seconds)", EVENT_MAXIMUM_ERRORS_IN_INTERVAL, EVENT_ERROR_INTERVAL_SECONDS); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); - eventEndpoint.close(); + eventEndpoint.closeConnection(); } if ((System.currentTimeMillis() - eventIntervalTimestamp) > 1000 @@ -1298,6 +1182,7 @@ public class TeslaVehicleHandler extends BaseThingHandler { if (Thread.interrupted()) { logger.debug("Event : The event thread was interrupted"); + eventEndpoint.close(); return; } } diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/ThingWebClientUtil.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/ThingWebClientUtil.java new file mode 100644 index 00000000000..75ea551bfc7 --- /dev/null +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/ThingWebClientUtil.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2010-2022 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.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ThingUID; + +/** + * {@link ThingWebClientUtil} provides an utility method to create a valid consumer name for web clients. + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class ThingWebClientUtil { + + private static final int MAX_CONSUMER_NAME_LENGTH = 20; + + /** + * Build a valid consumer name for HTTP or WebSocket client. + * + * @param uid thing UID for which to associate HTTP or WebSocket client + * @param prefix a prefix to consider for the name; can be null + * @return a valid consumer name + */ + public static String buildWebClientConsumerName(ThingUID uid, @Nullable String prefix) { + String pref = prefix == null ? "" : prefix; + String name = pref + uid.getAsString().replace(':', '-'); + if (name.length() > MAX_CONSUMER_NAME_LENGTH) { + // Try to use only prefix + binding ID + thing ID + name = pref + uid.getBindingId(); + if (name.length() > (MAX_CONSUMER_NAME_LENGTH / 2)) { + // Truncate the binding ID to keep enough place for thing ID + name = name.substring(0, MAX_CONSUMER_NAME_LENGTH / 2); + } + // Add the thing ID + String id = uid.getId(); + int maxIdLength = MAX_CONSUMER_NAME_LENGTH - 1 - name.length(); + if (id.length() > maxIdLength) { + // If thing ID is too big, use a hash code of the thing UID instead of thing id + // and truncate it if necessary + id = buildHashCode(uid, maxIdLength); + } + name += "-" + id; + } + return name; + } + + // Make the method public just to be able to call it inside the tests + static String buildHashCode(ThingUID uid, int maxLength) { + String result = Integer.toHexString(uid.hashCode()); + if (result.length() > maxLength) { + result = result.substring(result.length() - maxLength); + } + return result; + } +} diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/VehicleListener.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/VehicleListener.java index a72360c077a..265ff39a7b9 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/VehicleListener.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/handler/VehicleListener.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.tesla.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.tesla.internal.protocol.Vehicle; import org.openhab.binding.tesla.internal.protocol.VehicleConfig; @@ -21,12 +23,14 @@ import org.openhab.binding.tesla.internal.protocol.VehicleConfig; * * @author Kai Kreuzer - Initial contribution */ +@NonNullByDefault public interface VehicleListener { /** * This method is called by the {@link TeslaAccountHandler}, if a vehicle is identified. * * @param vehicle a vehicle that was found within an account. + * @param vehicleConfig vehicle configuration that was read from the vehicle or null, if not available. */ - void vehicleFound(Vehicle vehicle, VehicleConfig vehicleConfig); + void vehicleFound(Vehicle vehicle, @Nullable VehicleConfig vehicleConfig); } diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleConfig.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleConfig.java index f2b04d296e8..674e7bf9538 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleConfig.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/protocol/VehicleConfig.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.tesla.internal.protocol; +import org.openhab.binding.tesla.internal.TeslaBindingConstants; +import org.openhab.core.thing.ThingTypeUID; + /** * The {@link VehicleConfig} is a data structure to capture * vehicle configuration variables sent by the Tesla Vehicle @@ -41,4 +44,20 @@ public class VehicleConfig { public String third_row_seats; public String trim_badging; public String wheel_type; + + public ThingTypeUID identifyModel() { + switch (car_type) { + case "models": + case "models2": + return TeslaBindingConstants.THING_TYPE_MODELS; + case "modelx": + return TeslaBindingConstants.THING_TYPE_MODELX; + case "model3": + return TeslaBindingConstants.THING_TYPE_MODEL3; + case "modely": + return TeslaBindingConstants.THING_TYPE_MODELY; + default: + return TeslaBindingConstants.THING_TYPE_VEHICLE; + } + } } 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 00000000000..f773eca3e36 --- /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-2022 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() { + } +} diff --git a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/TimeProvider.java b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/TimeProvider.java index 25fe8400e43..0bfd642abbe 100644 --- a/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/TimeProvider.java +++ b/bundles/org.openhab.binding.tesla/src/main/java/org/openhab/binding/tesla/internal/throttler/TimeProvider.java @@ -18,12 +18,12 @@ package org.openhab.binding.tesla.internal.throttler; * @author Karel Goderis - Initial contribution */ public interface TimeProvider { - public static final TimeProvider SYSTEM_PROVIDER = new TimeProvider() { + static final TimeProvider SYSTEM_PROVIDER = new TimeProvider() { @Override public long getCurrentTimeInMillis() { return System.currentTimeMillis(); } }; - public long getCurrentTimeInMillis(); + long getCurrentTimeInMillis(); } diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties index 3dd21b33858..f42d75bd73a 100644 --- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/i18n/tesla.properties @@ -15,6 +15,8 @@ thing-type.tesla.modelx.label = Tesla Model X thing-type.tesla.modelx.description = A Tesla Model X Vehicle thing-type.tesla.modely.label = Tesla Model Y thing-type.tesla.modely.description = A Tesla Model Y Vehicle +thing-type.tesla.vehicle.label = Tesla +thing-type.tesla.vehicle.description = A Tesla Vehicle # thing types config @@ -24,6 +26,14 @@ thing-type.config.tesla.model3.allowWakeup.label = Allow Wake-Up thing-type.config.tesla.model3.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain! thing-type.config.tesla.model3.allowWakeupForCommands.label = Allow Wake-Up For Commands thing-type.config.tesla.model3.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long. +thing-type.config.tesla.model3.enableEvents.label = Enable Events +thing-type.config.tesla.model3.enableEvents.description = Enable the event stream for the vehicle +thing-type.config.tesla.model3.inactivity.label = Inactivity Interval +thing-type.config.tesla.model3.inactivity.description = The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. +thing-type.config.tesla.model3.useAdvancedStatesForPolling.label = Use Console Modes and Occupancy for Inactivity +thing-type.config.tesla.model3.useAdvancedStatesForPolling.description = Use these states to help continue the fast polling of the API. Do not back off polling if in these states. +thing-type.config.tesla.model3.useDriveState.label = Use Drive State for Inactivity +thing-type.config.tesla.model3.useDriveState.description = Use the drive state instead of location to determine vehicle inactivity. thing-type.config.tesla.model3.valetpin.label = Valet PIN thing-type.config.tesla.model3.valetpin.description = PIN to use when enabling Valet Mode thing-type.config.tesla.model3.vin.label = Vehicle Identification Number @@ -32,6 +42,14 @@ thing-type.config.tesla.models.allowWakeup.label = Allow Wake-Up thing-type.config.tesla.models.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain! thing-type.config.tesla.models.allowWakeupForCommands.label = Allow Wake-Up For Commands thing-type.config.tesla.models.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long. +thing-type.config.tesla.models.enableEvents.label = Enable Events +thing-type.config.tesla.models.enableEvents.description = Enable the event stream for the vehicle +thing-type.config.tesla.models.inactivity.label = Inactivity Interval +thing-type.config.tesla.models.inactivity.description = The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. +thing-type.config.tesla.models.useAdvancedStatesForPolling.label = Use Console Modes and Occupancy for Inactivity +thing-type.config.tesla.models.useAdvancedStatesForPolling.description = Use these states to help continue the fast polling of the API. Do not back off polling if in these states. +thing-type.config.tesla.models.useDriveState.label = Use Drive State for Inactivity +thing-type.config.tesla.models.useDriveState.description = Use the drive state instead of location to determine vehicle inactivity. thing-type.config.tesla.models.valetpin.label = Valet PIN thing-type.config.tesla.models.valetpin.description = PIN to use when enabling Valet Mode thing-type.config.tesla.models.vin.label = Vehicle Identification Number @@ -40,6 +58,14 @@ thing-type.config.tesla.modelx.allowWakeup.label = Allow Wake-Up thing-type.config.tesla.modelx.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain! thing-type.config.tesla.modelx.allowWakeupForCommands.label = Allow Wake-Up For Commands thing-type.config.tesla.modelx.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long. +thing-type.config.tesla.modelx.enableEvents.label = Enable Events +thing-type.config.tesla.modelx.enableEvents.description = Enable the event stream for the vehicle +thing-type.config.tesla.modelx.inactivity.label = Inactivity Interval +thing-type.config.tesla.modelx.inactivity.description = The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. +thing-type.config.tesla.modelx.useAdvancedStatesForPolling.label = Use Console Modes and Occupancy for Inactivity +thing-type.config.tesla.modelx.useAdvancedStatesForPolling.description = Use these states to help continue the fast polling of the API. Do not back off polling if in these states. +thing-type.config.tesla.modelx.useDriveState.label = Use Drive State for Inactivity +thing-type.config.tesla.modelx.useDriveState.description = Use the drive state instead of location to determine vehicle inactivity. thing-type.config.tesla.modelx.valetpin.label = Valet PIN thing-type.config.tesla.modelx.valetpin.description = PIN to use when enabling Valet Mode thing-type.config.tesla.modelx.vin.label = Vehicle Identification Number @@ -48,10 +74,34 @@ thing-type.config.tesla.modely.allowWakeup.label = Allow Wake-Up thing-type.config.tesla.modely.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain! thing-type.config.tesla.modely.allowWakeupForCommands.label = Allow Wake-Up For Commands thing-type.config.tesla.modely.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long. +thing-type.config.tesla.modely.enableEvents.label = Enable Events +thing-type.config.tesla.modely.enableEvents.description = Enable the event stream for the vehicle +thing-type.config.tesla.modely.inactivity.label = Inactivity Interval +thing-type.config.tesla.modely.inactivity.description = The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. +thing-type.config.tesla.modely.useAdvancedStatesForPolling.label = Use Console Modes and Occupancy for Inactivity +thing-type.config.tesla.modely.useAdvancedStatesForPolling.description = Use these states to help continue the fast polling of the API. Do not back off polling if in these states. +thing-type.config.tesla.modely.useDriveState.label = Use Drive State for Inactivity +thing-type.config.tesla.modely.useDriveState.description = Use the drive state instead of location to determine vehicle inactivity. thing-type.config.tesla.modely.valetpin.label = Valet PIN thing-type.config.tesla.modely.valetpin.description = PIN to use when enabling Valet Mode thing-type.config.tesla.modely.vin.label = Vehicle Identification Number thing-type.config.tesla.modely.vin.description = VIN of the vehicle +thing-type.config.tesla.vehicle.allowWakeup.label = Allow Wake-Up +thing-type.config.tesla.vehicle.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain! +thing-type.config.tesla.vehicle.allowWakeupForCommands.label = Allow Wake-Up For Commands +thing-type.config.tesla.vehicle.allowWakeupForCommands.description = Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in this case and you could cause the vehicle to stay awake very long. +thing-type.config.tesla.vehicle.enableEvents.label = Enable Events +thing-type.config.tesla.vehicle.enableEvents.description = Enable the event stream for the vehicle +thing-type.config.tesla.vehicle.inactivity.label = Inactivity Interval +thing-type.config.tesla.vehicle.inactivity.description = The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. +thing-type.config.tesla.vehicle.useAdvancedStatesForPolling.label = Use Console Modes and Occupancy for Inactivity +thing-type.config.tesla.vehicle.useAdvancedStatesForPolling.description = Use these states to help continue the fast polling of the API. Do not back off polling if in these states. +thing-type.config.tesla.vehicle.useDriveState.label = Use Drive State for Inactivity +thing-type.config.tesla.vehicle.useDriveState.description = Use the drive state instead of location to determine vehicle inactivity. +thing-type.config.tesla.vehicle.valetpin.label = Valet PIN +thing-type.config.tesla.vehicle.valetpin.description = PIN to use when enabling Valet Mode +thing-type.config.tesla.vehicle.vin.label = Vehicle Identification Number +thing-type.config.tesla.vehicle.vin.description = VIN of the vehicle # channel types @@ -206,7 +256,7 @@ channel-type.tesla.minavailabletemp.label = Minimum Temperature channel-type.tesla.minavailabletemp.description = Indicates the minimal inside temperature of the vehicle channel-type.tesla.mobileenabled.label = Mobile Enabled channel-type.tesla.mobileenabled.description = Indicates whether the vehicle can be remotely controlled -channel-type.tesla.notenoughpower.label = Not Enought Power +channel-type.tesla.notenoughpower.label = Not Enough Power channel-type.tesla.notenoughpower.description = Indicates if not enough power (ON) is available to heat the vehicle channel-type.tesla.notificationsenabled.label = Notifications Enabled channel-type.tesla.notificationsenabled.description = Not documented / To be defined diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml index 1d092f763f1..7355d5f368e 100644 --- a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/channels.xml @@ -421,7 +421,7 @@ Switch - + Indicates if not enough power (ON) is available to heat the vehicle diff --git a/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/vehicle.xml b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/vehicle.xml new file mode 100644 index 00000000000..e59b5912de8 --- /dev/null +++ b/bundles/org.openhab.binding.tesla/src/main/resources/OH-INF/thing/vehicle.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + A Tesla Vehicle + + + + + VIN of the vehicle + + + password + + PIN to use when enabling Valet Mode + + + false + + true + Allows waking up the vehicle. Caution: This can result in huge vampire drain! + + + false + + Allows waking up the vehicle, when commands are sent to it. Execution of commands will be delayed in + this case and you could cause the vehicle to stay awake very long. + + + false + + true + Enable the event stream for the vehicle + + + + true + The inactivity period in minutes, after which the binding stops for 20 minutes to let the car sleep. + 5 + + + false + + true + Use the drive state instead of location to determine vehicle inactivity. + + + false + + true + Use these states to help continue the fast polling of the API. Do not back off polling if in these + states. + + + + + +