added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.tesla</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,251 @@
# Tesla Binding
This binding integrates [Tesla Electrical Vehicles](https://www.tesla.com).
The integration happens through the Tesla Owners Remote API.
## Supported Things
All current Tesla models are supported by this binding. Access is established through a Tesla account as a bridge.
| Thing Type | Description |
|------------|----------------------------------------------|
| account | The account provides access to the vehicles. |
| models | A Tesla Model S |
| model3 | A Tesla Model 3 |
| modelx | A Tesla Model X |
| modely | A Tesla Model Y |
![Tesla](doc/tesla.jpg)
## Auto Discovery
If the authentication with the Tesla Account is done through the openHAB console (see "Bridge Configuration" option 1 below), the account is automatically added to the Inbox.
Furthermore, once an account is configured, it is automatically queried for associated vehicles and an Inbox entry is created for each of them.
## Bridge Configuration
The `account` bridge requires an OAuth2 refresh token as the only parameter `refreshToken`.
There are different ways of getting hold of the token:
1. Use the openHAB console
Run the following command on the console and provide your Tesla account credentials (the same that you use in the official Tesla app):
```
openhab> smarthome:tesla login
Username (email): mail@example.com
Password: topsecret
Attempting login...Attempting login...
Refresh token: xxxxxxxxxx
```
When successfully doing the login through the console, openHAB will automatically create an Inbox entry that is preconfigured with the refresh token, which you can now simply approve.
Alternatively, you can use the refresh token to textually configure your `account` bridge or to enter it in a manually created "Tesla Account" thing in the UI.
2. Provide your credentials in the UI
If you do not want to use the openHAB console, you can also manually create a "Tesla Account" thing in the UI and provide your username and password as parameters (to show them, use the "Show More" button) in the "Edit Thing" view and leave the refresh token parameter field empty.
openHAB will use the provided credentials to retrieve and set the refresh token and automatically delete your password from the configuration afterwards for safety reasons.
## Thing Configuration
The vehicle Things requires the vehicle's VIN as a configuration parameter `vin`.
Additionally, the optional boolean parameter `allowWakeup` can be set. 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).
## Channels
All vehicles support a huge number of channels - the following list shows the standard ones:
| Channel ID | Item Type | Label | Description |
|------------------|--------------------|--------------------|---------------------------------------------------------------------------------------------|
| autoconditioning | Switch | Auto Conditioning | Turns on auto-conditioning (a/c or heating) |
| batterylevel | Number | Battery Level | State of the battery in % |
| chargingstate | String | Charging State | “Starting”, “Complete”, “Charging”, “Disconnected”, “Stopped”, “NoPower” |
| chargeport | Switch | Charge Port | Open the Charge Port (ON) or indicates the state of the Charge Port (ON/OFF if Open/Closed) |
| climate | Switch | Climate | Climate status indicator |
| doorlock | Switch | Door Lock | Lock or unlock the car |
| insidetemp | Number:Temperature | Inside Temperature | Indicates the inside temperature of the vehicle |
| location | Location | Location | The actual position of the vehicle |
| odometer | Number:Length | Odometer | Odometer of the vehicle |
| speed | Number:Speed | Speed | Vehicle speed |
Additionally, these advanced channels are available (while not all of them are available on all vehicle types, like e.g. the sunroof):
| Channel ID | Item Type | Label | Description |
|---------------------------|--------------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------|
| autoparkstate | String | Autopark State | Undocumented / To be defined |
| autoparkstyle | String | Autopark Style | Undocumented / To be defined |
| batterycurrent | Number:ElectricCurrent | Battery Current | Current (Ampere) floating into (+) or out (-) of the battery |
| batteryheater | Switch | Battery Heater | Indicates if the battery heater is switched on |
| 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 | Switch | Central Display State | Indicates the state of the central display in the vehicle |
| 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 |
| chargecurrent | Number:ElectricCurrent | Charge Current | Current (Ampere) requested from the charger |
| chargeenablerequest | Switch | Charge Enable Request | Undocumented / To be defined |
| chargeenergyadded | Number:Energy | Charge Energy Added | Energy added, in kWh, during the last charging session |
| chargelimit | Dimmer | Charge Limit | Limit charging of the vehicle to the given % |
| chargelimitmaximum | Dimmer | Charge Limit Maximum | Maximum charging limit of the vehicle, as % |
| chargelimitminimum | Dimmer | Charge Limit Minimum | Minimum charging limit of the vehicle, as % |
| chargelimitsocstandard | Dimmer | Charge Limit SOC Standard | Standard charging limity of the vehicle, in % |
| chargeidealdistanceadded | Number:Length | "Ideal" Charge Distance Added | "Ideal" range added during the last charging session |
| chargemaxcurrent | Number:ElectricCurrent | Charge Max Current | Maximum current (Ampere) that can be requested from the charger |
| chargerateddistanceadded | Number:Length | "Rated" Charge Distance Added | "Rated" range added during the last charging session |
| chargerate | Number:Speed | Charge Rate | Distance per hour charging rate |
| chargestartingrange | String | Charge Starting Range | Undocumented / To be defined |
| chargestartingsoc | String | Charge Starting SOC | Undocumented / To be defined |
| chargetomax | Switch | Charge To Max Range | Indicates if the charging to the maximum range is enabled |
| chargercurrent | Number:ElectricCurrent | Charge Current | Current (Ampere) actually being drawn from the charger |
| chargerphases | Number | Charger Phases | Indicates the number of phases (1 to 3) used for charging |
| chargermaxcurrent | Number:ElectricCurrent | Charger Maximum Current | Maximum current (Ampere) that can be delivered by the charger |
| chargerpower | Number | Charger Power | Power actually delivered by the charger |
| chargervoltage | Number:ElectricPotential | Charger Voltage | Voltage (V) actually presented by the charger |
| driverfrontdoor | Contact | Driver Front Door | Indicates if the front door at the driver's side is opened |
| driverreardoor | Contact | Driver Rear Door | Indicates if the rear door at the driver's side is opened |
| drivertemp | Number:Temperature | Driver Temperature | Indicates the auto conditioning temperature set at the driver's side |
| eventstamp | DateTime | Event Timestamp | Timestamp of the last event received from the Tesla streaming service |
| estimatedbatteryrange | Number:Length | Estimated Battery Range | Estimated battery range |
| estimatedrange | Number | Estimated Range | Estimated range of the vehicle |
| fan | Number | Fan | Indicates the speed (0-7) of the fan |
| flashlights | Switch | Flash Lights | Flash the lights of the car (when ON is received) |
| frontdefroster | Switch | Front Defroster | Indicates if the front defroster is enabled |
| fronttrunk | Switch | Front Trunk | Indicates if the front trunk is opened, or open the front trunk when ON is received |
| gpstimestamp | DateTime | GPS Time Stamp | Time stamp of the most recent GPS location of the vehicle |
| heading | Number | Heading | Indicates the (compass) heading of the car, in 0-360 degrees |
| headingestimation | Number | Estimated Heading | Estimated (compass) heading of the car, in 0 to 360 degrees |
| honkhorn | Switch | Honk the Horn | Honk the horn of the vehicle, when ON is received |
| homelink | Switch | Homelink Nearby | Indicates if the Home Link is nearby |
| idealbatteryrange | Number:Length | Ideal Battery Range | Indicates the Batter Range |
| lefttempdirection | Number | Left Temperature Direction | Not documented / To be defined |
| lastautoparkerror | String | Last Autopark Error | Not documented / To be defined |
| location" advanced="false | Location | Location | The actual position of the vehicle |
| leftseatheater | Switch | Left Seat Heater | Indicates if the left seat heater is switched on |
| leftrearseatheater | Switch | Left Rear Seat Heater | Indicates if the left rear seat heater is switched on |
| leftrearbackseatheater | Number | Left Rear Backseat Heater | Indicates the level (0,1,2 or 3) of the left rear backseat heater |
| managedcharging | Switch | Managed Charging | Indicates managed charging is active |
| managedchargingcancelled | Switch | Managed Charging Cancelled | Indicates managed charging is cancelled by the user |
| managedchargingstart | String | Managed Charging Start Time | Not documented / To be defined |
| maxcharges | Number | Max Charges | Indicates the number of consecutive "Max Range Charges" performed by the vehicle |
| minavailabletemp | Number:Temperature | Minimum Temperature | Indicates the minimal inside temperature of the vehicle |
| maxavailabletemp | Number:Temperature | Maximum Temperature | Indicates the maximum inside temperature of the vehicle |
| mobileenabled | Switch | Mobile Enabled | Indicates whether the vehicle can be remotely controlled |
| notenoughpower | Switch | Not Enought Power | Indicates if not enough power (ON) is available to heat the vehicle |
| notificationsenabled | Switch | Notifications Enabled | Not documented / To be defined |
| notificationssupported | Switch | Notifications Supported | Not documented / To be defined |
| outsidetemp | Number:Temperature | Outside Temperature | Indicates the outside temperature of the vehicle |
| parsedcalendar | Switch | Parsed Calendar Supported | Not documented / To be defined |
| passengertemp | Number | Passenger Temperature | Indicates the auto conditioning temperature set at the passenger's side |
| passengerfrontdoor | Contact | Passenger Front Door | Indicates if the front door at the passenger's side is opened |
| passengerreardoor | Contact | Passenger Rear Door | Indicates if the rear door at the passenger's side is opened |
| power | Number | Power | Net kW flowing in (+) or out (-) of the battery |
| preconditioning | Switch | Preconditioning | Indicates if preconditioning is activated |
| range | Number | Range | Vehicle range - Not documented / To be defined |
| reardefroster | Switch | Rear Defroster | Indicates if the rear defroster is enabled |
| remotestartenabled | Switch | Remote Start | Not documented / To be defined |
| reartrunk | Switch | Rear Trunk | Indicates if the rear trunk is opened, or open/close the rear trunk when ON/OFF is received |
| remotestart | Switch | Remote Start | Not documented / To be defined |
| remotestartsupported | Switch | Remote Start Supported | Not documented / To be defined |
| rightseatheater | Switch | Right Seat Heater | Indicates if the right seat heater is switched on |
| rightrearseatheater | Switch | Right Rear Seat Heater | Indicates if the right rear seat heater is switched on |
| rightrearbackseatheater | Number | Right Rear Backseat Heater | Indicates the level (0,1,2 or 3) of the right rear backseat heater |
| righttempdirection | Number | Right Temperature Direction | Not documented / To be defined |
| scheduledchargingpending | Switch | Scheduled Charging Pending | Indicates if a scheduled charging session is still pending |
| scheduledchargingstart | DateTime | Scheduled Charging Start | Indicates when the scheduled charging session will start, in yyyy-MM-dd'T'HH:mm:ss format |
| shiftstate | String | Shift State | Indicates the state of the transmission, “P”, “D”, “R”, or “N” |
| sidemirrorheaters | Switch | Side Mirror Heaters | Indicates if the side mirror heaters are switched on |
| smartpreconditioning | Switch | Smart Preconditioning | Indicates if smart preconditioning is switched on |
| soc | Number | State of Charge | State of Charge, in % |
| state | String | State | “online”, “asleep”, “waking” |
| steeringwheelheater | Switch | Steering Wheel Heater | Indicates if the steering wheel heater is switched on |
| sunroofstate | String | Sunroof State | “unknown”, “open”, “closed”, “vent”, “comfort” |
| sunroof | Dimmer | Sunroof | Open or close the sunroof to provided % (0 closed, 100 fully open) |
| temperature | Number:Temperature | Temperature | Set the temperature of the autoconditioning system. The temperature for the driver and passenger will be synced. |
| timetofullcharge | Number | Time To Full Charge | Number of hours to fully charge the battery |
| tripcharging | Switch | Trip Charging | Not documented / To be defined |
| usablebatterylevel | Number | Usable Battery Level | Indicates the % of battery that can be used for vehicle functions like driving |
| userchargeenablerequest | String | User Charge Enable Request | Not documented / To be defined |
| valetmode | Switch | Valet Mode | Enable or disable Valet Mode |
| valetpin | Switch | Valet PIN Required | Indicates if a PIN code is required to disable valet mode |
| 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:
```
Bridge tesla:account:myaccount "My Tesla Account" [ refreshToken="xxxx" ] {
model3 mycar "My favorite car" [ vin="5YJSA7H25FFP53736"]
}
```
demo.items:
```
Switch TeslaCharge {channel="tesla:model3:myaccount:mycar:charge"}
Location TeslaLocation {channel="tesla:model3:myaccount:mycar:location"}
Dimmer TeslaChargeLimit {channel="tesla:model3:myaccount:mycar:chargelimit"}
String TeslaChargeRate {channel="tesla:model3:myaccount:mycar:chargerate"}
String TeslaChargingState {channel="tesla:model3:myaccount:mycar:chargingstate"}
Number TeslaTimeToFullCharge {channel="tesla:model3:myaccount:mycar:timetofullcharge"}
Number TeslaChargerPower {channel="tesla:model3:myaccount:mycar:chargerpower"}
DateTime TeslaScheduledChargingStart {channel="tesla:model3:myaccount:mycar:scheduledchargingstart"}
Dimmer TeslaSoC {channel="tesla:model3:myaccount:mycar:soc"}
Number:Speed TeslaSpeed {channel="tesla:model3:myaccount:mycar:speed"}
String TeslaState {channel="tesla:model3:myaccount:mycar:state"}
Number TeslaPower {channel="tesla:model3:myaccount:mycar:power"}
Number:Temperature TeslaInsideTemperature {channel="tesla:model3:myaccount:mycar:insidetemp"}
Number:Temperature TeslaOutsideTemperature {channel="tesla:model3:myaccount:mycar:outsidetemp"}
Switch TeslaAutoconditioning {channel="tesla:model3:myaccount:mycar:autoconditioning"}
Number:Temperature TeslaTemperature {channel="tesla:model3:myaccount:mycar:temperature"}
String TeslaShiftState {channel="tesla:model3:myaccount:mycar:shiftstate"}
Number TeslaBatteryCurrent {channel="tesla:model3:myaccount:mycar:batterycurrent"}
Number TeslaBatteryLevel {channel="tesla:model3:myaccount:mycar:batterylevel"}
DateTime TeslaEventstamp {channel="tesla:model3:myaccount:mycar:eventstamp"}
Number:Length TeslaOdometer {channel="tesla:model3:myaccount:mycar:odometer"}
Number TeslaHeading {channel="tesla:model3:myaccount:mycar:heading"}
DateTime TeslaGPSStamp {channel="tesla:model3:myaccount:mycar:gpstimestamp"}
```
demo.sitemap:
```
sitemap demo label="Main Menu"
{
Text label="Car" {
Text label="Drive" {
Text item=TeslaEventstamp label="Last Event Timestamp [%1$td.%1$tm.%1$tY %1$tT]"
Text item=TeslaState label="State [%s]"
Text item=TeslaSpeed label="Speed [%.1f]"
Text item=TeslaShiftState label="Shift State [%s]"
Text item=TeslaOdometer label="Odometer [%.1f km]"
}
Text label="Climate" {
Switch item=TeslaAutoconditioning label="Auto Conditioning" mappings=[ON=ON, OFF=OFF ]
Setpoint item=TeslaTemperature step=0.5 minValue=18 maxValue=34 label="Auto Conditioning Temperature [%.1f °C]" icon="temperature"
Text item=TeslaInsideTemperature label="Inside Temperature [%.1f]"
}
Text label="Power" {
Text item=TeslaBatteryCurrent label="Current [%.1f]"
}
Text item=TeslaSoC {
Switch item=TeslaCharge label="Charge" mappings=[ON=ON, OFF=OFF ]
Slider item=TeslaChargeLimit label="Charge Limit [%.1f]"
Text item=TeslaChargingState label="Charging State [%s]"
Text item=TeslaChargeRate label="Charge Rate [%s]"
Text item=TeslaScheduledChargingStart label="Charging Start [%1$td.%1$tm.%1$tY %1$tT]"
Text item=TeslaTimeToFullCharge label="Time To Full Charge [%.1f hours]"
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.tesla</artifactId>
<name>openHAB Add-ons :: Bundles :: Tesla Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.tesla-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-tesla" description="Tesla Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.tesla/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2010-2020 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;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link TeslaBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public class TeslaBindingConstants {
// REST URI constants
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_VEHICLE_ID = "/{vid}/";
public static final String PATH_WAKE_UP = "wake_up";
public static final String URI_ACCESS_TOKEN = "oauth/token";
public static final String URI_EVENT = "https://streaming.vn.teslamotors.com/stream/";
public static final String URI_OWNERS = "https://owner-api.teslamotors.com/";
public static final String VALETPIN = "valetpin";
public static final String VEHICLES = "vehicles";
public static final String VIN = "vin";
// Tesla REST API commands
public static final String COMMAND_ACTUATE_TRUNK = "actuate_trunk";
public static final String COMMAND_AUTO_COND_START = "auto_conditioning_start";
public static final String COMMAND_AUTO_COND_STOP = "auto_conditioning_stop";
public static final String COMMAND_CHARGE_MAX = "charge_max_range";
public static final String COMMAND_CHARGE_START = "charge_start";
public static final String COMMAND_CHARGE_STD = "charge_standard";
public static final String COMMAND_CHARGE_STOP = "charge_stop";
public static final String COMMAND_DOOR_LOCK = "door_lock";
public static final String COMMAND_DOOR_UNLOCK = "door_unlock";
public static final String COMMAND_FLASH_LIGHTS = "flash_lights";
public static final String COMMAND_HONK_HORN = "honk_horn";
public static final String COMMAND_OPEN_CHARGE_PORT = "charge_port_door_open";
public static final String COMMAND_RESET_VALET_PIN = "reset_valet_pin";
public static final String COMMAND_SET_CHARGE_LIMIT = "set_charge_limit";
public static final String COMMAND_SET_TEMP = "set_temps";
public static final String COMMAND_SET_VALET_MODE = "set_valet_mode";
public static final String COMMAND_SUN_ROOF = "sun_roof_control";
public static final String COMMAND_THROTTLE = "commandthrottle";
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_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");
public static final ThingTypeUID THING_TYPE_MODELY = new ThingTypeUID(BINDING_ID, "modely");
public enum EventKeys {
timestamp,
odometer,
speed,
soc,
elevation,
est_heading,
est_lat,
est_lng,
power,
shift_state,
range,
est_range,
heading
}
public static final String CHANNEL_CHARGE = "charge";
public static final String CHANNEL_COMBINED_TEMP = "combinedtemp";
// thing configurations
public static final String CONFIG_ALLOWWAKEUP = "allowWakeup";
public static final String CONFIG_ENABLEEVENTS = "enableEvents";
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
public static final String CONFIG_USERNAME = "username";
public static final String CONFIG_PASSWORD = "password";
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 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;
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.client.ClientBuilder;
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler;
import org.openhab.binding.tesla.internal.handler.TeslaVehicleHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
/**
* The {@link TeslaHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Karel Goderis - Initial contribution
* @author Nicolai Grødum - Adding token based auth
* @author Kai Kreuzer - Introduced account handler
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tesla")
public class TeslaHandlerFactory extends BaseThingHandlerFactory {
// TODO: Those constants are Jersey specific - once we move away from Jersey,
// this can be removed and the client builder creation simplified.
public static final String READ_TIMEOUT_JERSEY = "jersey.config.client.readTimeout";
public static final String CONNECT_TIMEOUT_JERSEY = "jersey.config.client.connectTimeout";
public static final String READ_TIMEOUT = "http.receive.timeout";
public static final String CONNECT_TIMEOUT = "http.connection.timeout";
private static final int EVENT_STREAM_CONNECT_TIMEOUT = 3000;
private static final int EVENT_STREAM_READ_TIMEOUT = 200000;
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.of(THING_TYPE_ACCOUNT, THING_TYPE_MODELS, THING_TYPE_MODEL3, THING_TYPE_MODELX, THING_TYPE_MODELY)
.collect(Collectors.toSet());
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
private ClientBuilder injectedClientBuilder;
private ClientBuilder clientBuilder;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_ACCOUNT)) {
return new TeslaAccountHandler((Bridge) thing, getClientBuilder().build());
} else {
return new TeslaVehicleHandler(thing, getClientBuilder());
}
}
private synchronized ClientBuilder getClientBuilder() {
if (clientBuilder == null) {
try {
clientBuilder = ClientBuilder.newBuilder();
clientBuilder.property(CONNECT_TIMEOUT_JERSEY, EVENT_STREAM_CONNECT_TIMEOUT);
clientBuilder.property(READ_TIMEOUT_JERSEY, EVENT_STREAM_READ_TIMEOUT);
} catch (Exception e) {
// we seem to have no Jersey, so let's hope for an injected builder by CXF
if (this.injectedClientBuilder != null) {
clientBuilder = injectedClientBuilder;
clientBuilder.property(CONNECT_TIMEOUT, EVENT_STREAM_CONNECT_TIMEOUT);
clientBuilder.property(READ_TIMEOUT, EVENT_STREAM_READ_TIMEOUT);
} else {
throw new IllegalStateException("No JAX RS Client Builder available.");
}
}
}
return clientBuilder;
}
}

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) 2010-2020 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.command;
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.binding.tesla.internal.discovery.TeslaAccountDiscoveryService;
import org.openhab.binding.tesla.internal.protocol.TokenRequest;
import org.openhab.binding.tesla.internal.protocol.TokenRequestPassword;
import org.openhab.binding.tesla.internal.protocol.TokenResponse;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.util.UIDUtils;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* Console commands for interacting with the Tesla integration
*
* @author Nicolai Grødum - Initial contribution
* @author Kai Kreuzer - refactored to use Tesla account thing
*/
@NonNullByDefault
@Component(service = ConsoleCommandExtension.class, immediate = true)
public class TeslaCommandExtension extends AbstractConsoleCommandExtension {
private static final String CMD_LOGIN = "login";
private final Logger logger = LoggerFactory.getLogger(TeslaCommandExtension.class);
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
private @Nullable ClientBuilder injectedClientBuilder;
private @Nullable WebTarget tokenTarget;
private final TeslaAccountDiscoveryService teslaAccountDiscoveryService;
@Activate
public TeslaCommandExtension(@Reference TeslaAccountDiscoveryService teslaAccountDiscoveryService) {
super("tesla", "Interact with the Tesla integration.");
this.teslaAccountDiscoveryService = teslaAccountDiscoveryService;
}
@Override
public void execute(String[] args, Console console) {
if (args.length > 0) {
String subCommand = args[0];
switch (subCommand) {
case CMD_LOGIN:
if (args.length == 1) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
console.print("Username (email): ");
String username = br.readLine();
console.println(username);
console.print("Password: ");
String pwd = br.readLine();
console.println("");
console.println("Attempting login...");
login(console, username, pwd);
} catch (Exception e) {
console.println(e.toString());
}
} else if (args.length == 3) {
login(console, args[1], args[2]);
} else {
printUsage(console);
}
break;
default:
console.println("Unknown command '" + subCommand + "'");
printUsage(console);
break;
}
}
}
@Override
public List<String> getUsages() {
return Arrays.asList(buildCommandUsage(CMD_LOGIN + " [<user email>] [<password>]",
"Authenticates the user and provides a refresh token."));
}
private void login(Console console, String username, String password) {
try {
Gson gson = new Gson();
TokenRequest token = new TokenRequestPassword(username, password);
String payLoad = gson.toJson(token);
Response response = getTokenTarget().request()
.post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE));
if (response != null) {
if (response.getStatus() == 200 && response.hasEntity()) {
String responsePayLoad = response.readEntity(String.class);
TokenResponse tokenResponse = gson.fromJson(responsePayLoad.trim(), TokenResponse.class);
console.println("Refresh token: " + tokenResponse.refresh_token);
ThingUID thingUID = new ThingUID(TeslaBindingConstants.THING_TYPE_ACCOUNT,
UIDUtils.encode(username));
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel("Tesla Account")
.withProperty(TeslaBindingConstants.CONFIG_REFRESHTOKEN, tokenResponse.refresh_token)
.withProperty(TeslaBindingConstants.CONFIG_USERNAME, username)
.withRepresentationProperty(TeslaBindingConstants.CONFIG_USERNAME).build();
teslaAccountDiscoveryService.thingDiscovered(result);
} else {
console.println(
"Failure: " + response.getStatus() + " " + response.getStatusInfo().getReasonPhrase());
}
}
} catch (Exception e) {
console.println("Failed to retrieve token: " + e.getMessage());
logger.error("Could not get refresh token.", e);
}
}
private synchronized WebTarget getTokenTarget() {
WebTarget target = this.tokenTarget;
if (target != null) {
return target;
} else {
Client client;
try {
client = ClientBuilder.newBuilder().build();
} catch (Exception e) {
// we seem to have no Jersey, so let's hope for an injected builder by CXF
if (this.injectedClientBuilder != null) {
client = injectedClientBuilder.build();
} else {
throw new IllegalStateException("No JAX RS Client Builder available.");
}
}
WebTarget teslaTarget = client.target(URI_OWNERS);
target = teslaTarget.path(URI_ACCESS_TOKEN);
this.tokenTarget = target;
return target;
}
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tesla.internal.TeslaHandlerFactory;
import org.openhab.binding.tesla.internal.command.TeslaCommandExtension;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryService;
import org.osgi.service.component.annotations.Component;
/**
* This is a discovery service, is used by the {@link TeslaCommandExtension} for
* automatically creating Tesla accounts.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@Component(service = { TeslaAccountDiscoveryService.class, DiscoveryService.class })
public class TeslaAccountDiscoveryService extends AbstractDiscoveryService {
public TeslaAccountDiscoveryService() throws IllegalArgumentException {
super(TeslaHandlerFactory.SUPPORTED_THING_TYPES_UIDS, 10, true);
}
@Override
protected void startScan() {
}
@Override
public void activate(@Nullable Map<@NonNull String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
public void thingDiscovered(DiscoveryResult discoveryResult) {
super.thingDiscovered(discoveryResult);
}
}

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2020 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.discovery;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.binding.tesla.internal.TeslaHandlerFactory;
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler;
import org.openhab.binding.tesla.internal.handler.VehicleListener;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This service is used by {@link TeslaAccountHandler} instances in order to
* automatically provide vehicle information from the account.
*
* @author Kai Kreuzer - Initial contribution
*
*/
@Component(service = ThingHandlerService.class)
public class TeslaVehicleDiscoveryService extends AbstractDiscoveryService
implements DiscoveryService, VehicleListener, ThingHandlerService {
private final Logger logger = LoggerFactory.getLogger(TeslaVehicleDiscoveryService.class);
public TeslaVehicleDiscoveryService() throws IllegalArgumentException {
super(TeslaHandlerFactory.SUPPORTED_THING_TYPES_UIDS, 10, true);
}
private TeslaAccountHandler handler;
@Override
public void setThingHandler(ThingHandler handler) {
this.handler = (TeslaAccountHandler) handler;
this.handler.addVehicleListener(this);
}
@Override
public ThingHandler getThingHandler() {
return handler;
}
@Override
protected void startScan() {
handler.scanForVehicles();
}
@Override
public void activate(@Nullable Map<@NonNull String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Override
public void deactivate() {
super.deactivate();
if (handler != null) {
handler.removeVehicleListener(this);
}
}
@Override
public void vehicleFound(Vehicle vehicle, VehicleConfig vehicleConfig) {
ThingTypeUID type = identifyModel(vehicleConfig);
if (type != null) {
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)
.build();
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;
}
}
}

View File

@@ -0,0 +1,547 @@
/**
* Copyright (c) 2010-2020 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 static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.binding.tesla.internal.discovery.TeslaVehicleDiscoveryService;
import org.openhab.binding.tesla.internal.protocol.TokenRequest;
import org.openhab.binding.tesla.internal.protocol.TokenRequestPassword;
import org.openhab.binding.tesla.internal.protocol.TokenRequestRefreshToken;
import org.openhab.binding.tesla.internal.protocol.TokenResponse;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* The {@link TeslaAccountHandler} is responsible for handling commands, which are sent
* to one of the channels.
*
* @author Karel Goderis - Initial contribution
* @author Nicolai Grødum - Adding token based auth
* @author Kai Kreuzer - refactored to use separate vehicle handlers
*/
public class TeslaAccountHandler extends BaseBridgeHandler {
public static final int API_MAXIMUM_ERRORS_IN_INTERVAL = 2;
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")
.withZone(ZoneId.systemDefault());
private final Logger logger = LoggerFactory.getLogger(TeslaAccountHandler.class);
// REST Client API variables
private final WebTarget teslaTarget;
private final WebTarget tokenTarget;
WebTarget vehiclesTarget; // this cannot be marked final as it is used in the runnable
final WebTarget vehicleTarget;
final WebTarget dataRequestTarget;
final WebTarget commandTarget;
final WebTarget wakeUpTarget;
// Threading and Job related variables
protected ScheduledFuture<?> connectJob;
protected long lastTimeStamp;
protected long apiIntervalTimestamp;
protected int apiIntervalErrors;
protected long eventIntervalTimestamp;
protected int eventIntervalErrors;
protected ReentrantLock lock;
private final Gson gson = new Gson();
private final JsonParser parser = new JsonParser();
private TokenResponse logonToken;
private final Set<VehicleListener> vehicleListeners = new HashSet<>();
public TeslaAccountHandler(Bridge bridge, Client teslaClient) {
super(bridge);
this.teslaTarget = teslaClient.target(URI_OWNERS);
this.tokenTarget = teslaTarget.path(URI_ACCESS_TOKEN);
this.vehiclesTarget = teslaTarget.path(API_VERSION).path(VEHICLES);
this.vehicleTarget = vehiclesTarget.path(PATH_VEHICLE_ID);
this.dataRequestTarget = vehicleTarget.path(PATH_DATA_REQUEST);
this.commandTarget = vehicleTarget.path(PATH_COMMAND);
this.wakeUpTarget = vehicleTarget.path(PATH_WAKE_UP);
}
@Override
public void initialize() {
logger.trace("Initializing the Tesla account handler for {}", this.getStorageKey());
updateStatus(ThingStatus.UNKNOWN);
lock = new ReentrantLock();
lock.lock();
try {
if (connectJob == null || connectJob.isCancelled()) {
connectJob = scheduler.scheduleWithFixedDelay(connectRunnable, 0, CONNECT_RETRY_INTERVAL,
TimeUnit.MILLISECONDS);
}
} finally {
lock.unlock();
}
}
@Override
public void dispose() {
logger.trace("Disposing the Tesla account handler for {}", getThing().getUID());
lock.lock();
try {
if (connectJob != null && !connectJob.isCancelled()) {
connectJob.cancel(true);
connectJob = null;
}
} finally {
lock.unlock();
}
}
public void scanForVehicles() {
scheduler.execute(() -> queryVehicles());
}
public void addVehicleListener(VehicleListener listener) {
this.vehicleListeners.add(listener);
}
public void removeVehicleListener(VehicleListener listener) {
this.vehicleListeners.remove(listener);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// we do not have any channels -> nothing to do here
}
public String getAuthHeader() {
if (logonToken != null) {
return "Bearer " + logonToken.access_token;
} else {
return null;
}
}
protected boolean checkResponse(Response response, boolean immediatelyFail) {
if (response != null && response.getStatus() == 200) {
return true;
} else {
apiIntervalErrors++;
if (immediatelyFail || apiIntervalErrors >= API_MAXIMUM_ERRORS_IN_INTERVAL) {
if (immediatelyFail) {
logger.warn("Got an unsuccessful result, setting vehicle to offline and will try again");
} else {
logger.warn("Reached the maximum number of errors ({}) for the current interval ({} seconds)",
API_MAXIMUM_ERRORS_IN_INTERVAL, API_ERROR_INTERVAL_SECONDS);
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
} else if ((System.currentTimeMillis() - apiIntervalTimestamp) > 1000 * API_ERROR_INTERVAL_SECONDS) {
logger.trace("Resetting the error counter. ({} errors in the last interval)", apiIntervalErrors);
apiIntervalTimestamp = System.currentTimeMillis();
apiIntervalErrors = 0;
}
}
return false;
}
protected Vehicle[] queryVehicles() {
String authHeader = getAuthHeader();
if (authHeader != null) {
// get a list of vehicles
Response response = vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", authHeader).get();
logger.debug("Querying the vehicle: Response: {}:{}", response.getStatus(), response.getStatusInfo());
if (!checkResponse(response, true)) {
logger.error("An error occurred while querying the vehicle");
return null;
}
JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
for (Vehicle vehicle : vehicleArray) {
String responseString = invokeAndParse(vehicle.id, VEHICLE_CONFIG, null, dataRequestTarget);
if (StringUtils.isBlank(responseString)) {
continue;
}
VehicleConfig vehicleConfig = gson.fromJson(responseString, VehicleConfig.class);
for (VehicleListener listener : vehicleListeners) {
listener.vehicleFound(vehicle, vehicleConfig);
}
for (Thing vehicleThing : getThing().getThings()) {
if (vehicle.vin.equals(vehicleThing.getConfiguration().get(VIN))) {
TeslaVehicleHandler vehicleHandler = (TeslaVehicleHandler) vehicleThing.getHandler();
if (vehicleHandler != null) {
logger.debug("Querying the vehicle: VIN {}", vehicle.vin);
String vehicleJSON = gson.toJson(vehicle);
vehicleHandler.parseAndUpdate("queryVehicle", null, vehicleJSON);
logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicle_id,
vehicle.tokens);
}
}
}
}
return vehicleArray;
} else {
return new Vehicle[0];
}
}
private String getStorageKey() {
return this.getThing().getUID().getId();
}
private ThingStatusInfo authenticate() {
TokenResponse token = logonToken;
boolean hasExpired = true;
if (token != null) {
Instant tokenCreationInstant = Instant.ofEpochMilli(token.created_at * 1000);
logger.debug("Found a request token created at {}", dateFormatter.format(tokenCreationInstant));
Instant tokenExpiresInstant = Instant.ofEpochMilli(token.created_at * 1000 + 60 * token.expires_in);
if (tokenExpiresInstant.isBefore(Instant.now())) {
logger.debug("The token has expired at {}", dateFormatter.format(tokenExpiresInstant));
hasExpired = true;
} else {
hasExpired = false;
}
}
if (hasExpired) {
String username = (String) getConfig().get(CONFIG_USERNAME);
String refreshToken = (String) getConfig().get(CONFIG_REFRESHTOKEN);
if (refreshToken == null || StringUtils.isEmpty(refreshToken)) {
if (!StringUtils.isEmpty(username)) {
String password = (String) getConfig().get(CONFIG_PASSWORD);
return authenticate(username, password);
} else {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Neither a refresh token nor credentials are provided.");
}
}
TokenRequestRefreshToken tokenRequest = null;
try {
tokenRequest = new TokenRequestRefreshToken(refreshToken);
} catch (GeneralSecurityException e) {
logger.error("An exception occurred while requesting a new token: '{}'", e.getMessage(), e);
}
String payLoad = gson.toJson(tokenRequest);
Response response = null;
try {
response = tokenTarget.request().post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE));
} catch (ProcessingException e) {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
logger.debug("Authenticating: Response: {}:{}", response.getStatus(), response.getStatusInfo());
if (response.getStatus() == 200 && response.hasEntity()) {
String responsePayLoad = response.readEntity(String.class);
TokenResponse tokenResponse = gson.fromJson(responsePayLoad.trim(), TokenResponse.class);
if (!refreshToken.equals(tokenResponse.refresh_token)) {
Configuration configuration = editConfiguration();
configuration.put(CONFIG_REFRESHTOKEN, tokenResponse.refresh_token);
updateConfiguration(configuration);
}
if (!StringUtils.isEmpty(tokenResponse.access_token)) {
this.logonToken = tokenResponse;
logger.trace("Access Token is {}", logonToken.access_token);
}
return new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
} else if (response.getStatus() == 401) {
if (!StringUtils.isEmpty(username)) {
String password = (String) getConfig().get(CONFIG_PASSWORD);
return authenticate(username, password);
} else {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Refresh token is not valid and no credentials are provided.");
}
} else {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"HTTP returncode " + response.getStatus());
}
}
return new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
}
private ThingStatusInfo authenticate(String username, String password) {
TokenRequest token = null;
try {
token = new TokenRequestPassword(username, password);
} catch (GeneralSecurityException e) {
logger.error("An exception occurred while building a password request token: '{}'", e.getMessage(), e);
}
if (token != null) {
String payLoad = gson.toJson(token);
Response response = tokenTarget.request().post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE));
if (response != null) {
logger.debug("Authenticating: Response : {}:{}", response.getStatus(), response.getStatusInfo());
if (response.getStatus() == 200 && response.hasEntity()) {
String responsePayLoad = response.readEntity(String.class);
TokenResponse tokenResponse = gson.fromJson(responsePayLoad.trim(), TokenResponse.class);
if (StringUtils.isNotEmpty(tokenResponse.access_token)) {
this.logonToken = tokenResponse;
Configuration cfg = editConfiguration();
cfg.put(TeslaBindingConstants.CONFIG_REFRESHTOKEN, logonToken.refresh_token);
cfg.remove(TeslaBindingConstants.CONFIG_PASSWORD);
updateConfiguration(cfg);
return new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
}
} else if (response.getStatus() == 401) {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Invalid credentials.");
} else {
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"HTTP returncode " + response.getStatus());
}
} else {
logger.debug("Authenticating: Response was null");
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Failed retrieving a response from the server.");
}
}
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Cannot build request from credentials.");
}
protected String invokeAndParse(String vehicleId, String command, String payLoad, WebTarget target) {
logger.debug("Invoking: {}", command);
if (vehicleId != null) {
Response response;
if (payLoad != null) {
if (command != null) {
response = target.resolveTemplate("cmd", command).resolveTemplate("vid", vehicleId).request()
.header("Authorization", "Bearer " + logonToken.access_token)
.post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE));
} else {
response = target.resolveTemplate("vid", vehicleId).request()
.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 {
response = target.resolveTemplate("vid", vehicleId).request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer " + logonToken.access_token).get();
}
}
if (!checkResponse(response, false)) {
logger.debug("An error occurred while communicating with the vehicle during request {}: {}:{}", command,
(response != null) ? response.getStatus() : "",
(response != null) ? response.getStatusInfo() : "No Response");
return null;
}
try {
JsonObject jsonObject = parser.parse(response.readEntity(String.class)).getAsJsonObject();
logger.trace("Request : {}:{}:{} yields {}", command, payLoad, target, jsonObject.get("response"));
return jsonObject.get("response").toString();
} catch (Exception e) {
logger.error("An exception occurred while invoking a REST request: '{}'", e.getMessage());
}
}
return null;
}
protected Runnable connectRunnable = () -> {
try {
lock.lock();
if (getThing().getStatus() != ThingStatus.ONLINE) {
logger.debug("Setting up an authenticated connection to the Tesla back-end");
ThingStatusInfo authenticationResult = authenticate();
updateStatus(authenticationResult.getStatus(), authenticationResult.getStatusDetail(),
authenticationResult.getDescription());
if (authenticationResult.getStatus() == ThingStatus.ONLINE) {
// get a list of vehicles
Response response = vehiclesTarget.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer " + logonToken.access_token).get();
if (response != null && response.getStatus() == 200 && response.hasEntity()) {
updateStatus(ThingStatus.ONLINE);
for (Vehicle vehicle : queryVehicles()) {
Bridge bridge = getBridge();
if (bridge != null) {
List<Thing> things = bridge.getThings();
for (int i = 0; i < things.size(); i++) {
Thing thing = things.get(i);
TeslaVehicleHandler handler = (TeslaVehicleHandler) thing.getHandler();
if (handler != null) {
if (vehicle.vin.equals(thing.getConfiguration().get(VIN))) {
logger.debug(
"Found the vehicle with VIN '{}' in the list of vehicles you own",
getConfig().get(VIN));
apiIntervalErrors = 0;
apiIntervalTimestamp = System.currentTimeMillis();
} else {
logger.warn(
"Unable to find the vehicle with VIN '{}' in the list of vehicles you own",
getConfig().get(VIN));
handler.updateStatus(ThingStatus.OFFLINE,
ThingStatusDetail.CONFIGURATION_ERROR,
"Vin is not available through this account.");
}
}
}
}
}
} else {
if (response != null) {
logger.error("Error fetching the list of vehicles : {}:{}", response.getStatus(),
response.getStatusInfo());
updateStatus(ThingStatus.OFFLINE);
}
}
}
}
} catch (Exception e) {
logger.error("An exception occurred while connecting to the Tesla back-end: '{}'", e.getMessage(), e);
} finally {
lock.unlock();
}
};
public static class Authenticator implements ClientRequestFilter {
private final String user;
private final String password;
public Authenticator(String user, String password) {
this.user = user;
this.password = password;
}
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
final String basicAuthentication = getBasicAuthentication();
headers.add("Authorization", basicAuthentication);
}
private String getBasicAuthentication() {
String token = this.user + ":" + this.password;
return "Basic " + Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
}
}
protected class Request implements Runnable {
private TeslaVehicleHandler handler;
private String request;
private String payLoad;
private WebTarget target;
public Request(TeslaVehicleHandler handler, String request, String payLoad, WebTarget target) {
this.handler = handler;
this.request = request;
this.payLoad = payLoad;
this.target = target;
}
@Override
public void run() {
try {
String result = "";
if (getThing().getStatus() == ThingStatus.ONLINE) {
result = invokeAndParse(handler.getVehicleId(), request, payLoad, target);
if (result != null && !"".equals(result)) {
handler.parseAndUpdate(request, payLoad, result);
}
}
} catch (Exception e) {
logger.error("An exception occurred while executing a request to the vehicle: '{}'", e.getMessage(), e);
}
}
}
public Request newRequest(TeslaVehicleHandler teslaVehicleHandler, String command, String payLoad,
WebTarget target) {
return new Request(teslaVehicleHandler, command, payLoad, target);
}
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singletonList(TeslaVehicleDiscoveryService.class);
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 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.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
/**
* The {@link VehicleListener} interface can be implemented by classes that want to be informed about
* existing vehicles of a given account. They need to register on an {@link TeslaAccountHandler}.
*
* @author Kai Kreuzer - Initial contribution
*/
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.
*/
void vehicleFound(Vehicle vehicle, VehicleConfig vehicleConfig);
}

View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2010-2020 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 ChargeState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class ChargeState {
public boolean battery_heater_on;
public boolean charge_enable_request;
public boolean charge_port_door_open;
public boolean charge_to_max_range;
public boolean eu_vehicle;
public boolean fast_charger_present;
public boolean managed_charging_active;
public boolean managed_charging_user_canceled;
public boolean motorized_charge_port;
public boolean not_enough_power_to_heat;
public boolean scheduled_charging_pending;
public boolean trip_charging;
public float battery_current;
public float battery_range;
public float charge_energy_added;
public float charge_miles_added_ideal;
public float charge_miles_added_rated;
public float charge_rate;
public float est_battery_range;
public float ideal_battery_range;
public float time_to_full_charge;
public int battery_level;
public int charge_current_request;
public int charge_current_request_max;
public int charge_limit_soc;
public int charge_limit_soc_max;
public int charge_limit_soc_min;
public int charge_limit_soc_std;
public int charger_actual_current;
public int charger_phases;
public int charger_pilot_current;
public int charger_power;
public int charger_voltage;
public int max_range_charge_counter;
public int usable_battery_level;
public String charge_port_latch;
public String charging_state;
public String conn_charge_cable;
public String fast_charger_brand;
public String fast_charger_type;
public String managed_charging_start_time;
public String scheduled_charging_start_time;
public String user_charge_enable_request;
ChargeState() {
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 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 ClimateState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class ClimateState {
public boolean battery_heater;
public boolean battery_heater_no_power;
public boolean is_auto_conditioning_on;
public boolean is_climate_on;
public boolean is_front_defroster_on;
public boolean is_preconditioning;
public boolean is_rear_defroster_on;
public int seat_heater_left;
public int seat_heater_rear_center;
public int seat_heater_rear_left;
public int seat_heater_rear_right;
public int seat_heater_right;
public boolean side_mirror_heaters;
public boolean smart_preconditioning;
public boolean steering_wheel_heater;
public boolean wiper_blade_heater;
public float driver_temp_setting;
public float inside_temp;
public float outside_temp;
public float passenger_temp_setting;
public int fan_status;
public int left_temp_direction;
public int max_avail_temp;
public int min_avail_temp;
public int right_temp_direction;
public int seat_heater_rear_left_back;
public int seat_heater_rear_right_back;
ClimateState() {
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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 DriveState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class DriveState {
public double latitude;
public double longitude;
public double native_latitude;
public double native_longitude;
public int gps_as_of;
public int heading;
public int native_location_supported;
public String native_type;
public String shift_state;
public String speed;
DriveState() {
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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 GUIState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class GUIState {
public String gui_distance_units;
public String gui_temperature_units;
public String gui_charge_rate_units;
public String gui_24_hour_time;
public String gui_range_display;
public GUIState() {
}
}

View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2020 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;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.SecretKeySpec;
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
/**
* The {@link TokenRequest} is a datastructure to capture
* authentication/credentials required to log into the
* Tesla Remote Service
*
* @author Karel Goderis - Initial contribution
* @author Nicolai Grødum - Adding token based auth
*/
@SuppressWarnings("unused")
public abstract class TokenRequest {
private String client_id;
private String client_secret;
TokenRequest() throws GeneralSecurityException {
byte[] ci = { 115, -51, 67, -104, -107, 16, -116, -114, -11, -120, 41, 84, -106, -15, -67, 78, -10, -24, -47,
124, 35, 73, 10, 43, -9, 123, 127, 126, -114, 58, 23, 3, 115, -70, -115, 46, 17, 87, -115, 31, -67, -90,
-107, -100, 59, 18, -19, 91, 95, -52, 82, 91, -37, -83, -74, 39, 12, 59, 14, -81, 3, 95, -111, 72 };
byte[] cs = { -28, 97, -94, 108, 69, -40, 111, 53, 88, -57, 82, 111, 57, 98, 116, -63, -75, -37, 16, 95, 2,
-113, -46, -112, 32, 73, -43, 23, -114, 38, -110, -85, -42, 41, 98, 118, 30, -2, -11, 93, 22, 89, 56,
105, -128, 20, -24, -108, 76, 31, -19, 60, 69, -98, -122, 54, 67, 19, 72, -37, 106, 62, -120, -52 };
SecretKeySpec key = new SecretKeySpec(TeslaBindingConstants.API_NAME.getBytes(), "AES");
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/ECB/NoPadding");
byte[] plainText = new byte[ci.length];
cipher.init(Cipher.DECRYPT_MODE, key);
int ptLength = cipher.update(ci, 0, ci.length, plainText, 0);
cipher.doFinal(plainText, ptLength);
this.client_id = new String(plainText);
cipher.init(Cipher.DECRYPT_MODE, key);
ptLength = cipher.update(cs, 0, cs.length, plainText, 0);
cipher.doFinal(plainText, ptLength);
this.client_secret = new String(plainText);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | ShortBufferException
| IllegalBlockSizeException | BadPaddingException e) {
throw e;
}
}
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2010-2020 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;
import java.security.GeneralSecurityException;
/**
* The {@link TokenRequestPassword} is a datastructure to capture
* authentication/credentials required to log into the
* Tesla Remote Service
*
* @author Karel Goderis - Initial contribution
* @author Nicolai Grødum - Adding token based auth
*/
@SuppressWarnings("unused")
public class TokenRequestPassword extends TokenRequest {
private String grant_type = "password";
private String email;
private String password;
public TokenRequestPassword(String email, String password) throws GeneralSecurityException {
super();
this.email = email;
this.password = password;
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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;
import java.security.GeneralSecurityException;
/**
* The {@link TokenRequestRefreshToken} is a datastructure to capture
* authentication/credentials required to log into the
* Tesla Remote Service
*
* @author Nicolai Grødum - Adding token based auth
*/
public class TokenRequestRefreshToken extends TokenRequest {
private String grant_type = "refresh_token";
private String refresh_token;
public TokenRequestRefreshToken(String refresh_token) throws GeneralSecurityException {
super();
this.refresh_token = refresh_token;
}
}

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2010-2020 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 TokenResponse} is a datastructure to capture
* authentication response from Tesla Remote Service
*
* @author Nicolai Grødum
*/
public class TokenResponse {
public String access_token;
public String token_type;
public Long expires_in;
public Long created_at;
public String refresh_token;
public TokenResponse() {
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 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 Vehicle} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class Vehicle {
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;
Vehicle() {
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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 VehicleConfig} is a data structure to capture
* vehicle configuration variables sent by the Tesla Vehicle
*
* @author Dan Cunningham - Initial contribution
*/
public class VehicleConfig {
public boolean can_accept_navigation_requests;
public boolean can_actuate_trunks;
public boolean eu_vehicle;
public boolean has_air_suspension;
public boolean has_ludicrous_mode;
public boolean motorized_charge_port;
public boolean plg;
public boolean rhd;
public boolean use_range_badging;
public int rear_seat_heaters;
public int rear_seat_type;
public int sun_roof_installed;
public long timestamp;
public String car_special_type;
public String car_type;
public String charge_port_type;
public String exterior_color;
public String roof_color;
public String spoiler_type;
public String third_row_seats;
public String trim_badging;
public String wheel_type;
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 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 VehicleState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class VehicleState {
public boolean dark_rims;
public boolean has_spoiler;
public boolean homelink_nearby;
public boolean locked;
public boolean notifications_supported;
public boolean parsed_calendar_supported;
public boolean remote_start;
public boolean remote_start_supported;
public boolean rhd;
public boolean valet_mode;
public boolean valet_pin_needed;
public float odometer;
public int center_display_state;
public int df;
public int dr;
public int ft;
public int pf;
public int pr;
public int rear_seat_heaters;
public int rt;
public int seat_type;
public int sun_roof_installed;
public int sun_roof_percent_open;
public String autopark_state;
public String autopark_state_v2;
public String autopark_style;
public String car_version;
public String exterior_color;
public String last_autopark_error;
public String perf_config;
public String roof_color;
public String sun_roof_state;
public String vehicle_name;
public String wheel_type;
VehicleState() {
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
/**
* The {@link AbstractChannelThrottler} is abstract class implementing a
* throttler with one global execution rate, or rate limiter
*
* @author Karel Goderis - Initial contribution
*/
abstract class AbstractChannelThrottler implements ChannelThrottler {
protected final Rate totalRate;
protected final TimeProvider timeProvider;
protected final ScheduledExecutorService scheduler;
protected final Map<Object, Rate> channels = new HashMap<>();
protected AbstractChannelThrottler(Rate totalRate, ScheduledExecutorService scheduler, Map<Object, Rate> channels,
TimeProvider timeProvider) {
this.totalRate = totalRate;
this.scheduler = scheduler;
this.channels.putAll(channels);
this.timeProvider = timeProvider;
}
protected synchronized long callTime(Rate channel) {
long now = timeProvider.getCurrentTimeInMillis();
long callTime = totalRate.callTime(now);
if (channel != null) {
callTime = Math.max(callTime, channel.callTime(now));
channel.addCall(callTime);
}
totalRate.addCall(callTime);
return callTime;
}
protected long getThrottleDelay(Object channelKey) {
long delay = callTime(channels.get(channelKey)) - timeProvider.getCurrentTimeInMillis();
return delay < 0 ? 0 : delay;
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
/**
* The {@link AbstractMultiRateChannelThrottler} is abstract class implementing
* a throttler with multiple global execution rates, or rate limiters
*
* @author Karel Goderis - Initial contribution
*/
abstract class AbstractMultiRateChannelThrottler implements ChannelThrottler {
protected final TimeProvider timeProvider;
protected final ScheduledExecutorService scheduler;
protected final Map<Object, Rate> channels = new HashMap<>();
protected final ArrayList<Rate> rates = new ArrayList<>();
protected AbstractMultiRateChannelThrottler(Rate rate, ScheduledExecutorService scheduler,
Map<Object, Rate> channels, TimeProvider timeProvider) {
this.rates.add(rate);
this.scheduler = scheduler;
this.channels.putAll(channels);
this.timeProvider = timeProvider;
}
public synchronized void addRate(Rate rate) {
this.rates.add(rate);
}
protected synchronized long callTime(Rate channel) {
long maxCallTime = 0;
long finalCallTime = 0;
long now = timeProvider.getCurrentTimeInMillis();
Iterator<Rate> iterator = rates.iterator();
while (iterator.hasNext()) {
Rate someRate = iterator.next();
maxCallTime = Math.max(maxCallTime, someRate.callTime(now));
}
if (channel != null) {
finalCallTime = Math.max(maxCallTime, channel.callTime(now));
channel.addCall(finalCallTime);
}
iterator = rates.iterator();
while (iterator.hasNext()) {
Rate someRate = iterator.next();
someRate.addCall(finalCallTime);
}
return finalCallTime;
}
protected long getThrottleDelay(Object channelKey) {
long delay = callTime(channels.get(channelKey)) - timeProvider.getCurrentTimeInMillis();
return delay < 0 ? 0 : delay;
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.concurrent.Future;
/**
* The {@link ChannelThrottler} defines the interface for to submit tasks to a
* throttler
*
* @author Karel Goderis - Initial contribution
*/
public interface ChannelThrottler {
Future<?> submit(Runnable task);
Future<?> submit(Object channelKey, Runnable task);
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link QueueChannelThrottler} implements a throttler that maintains
* multiple execution rates, and maintains the order of calls
*
* @author Karel Goderis - Initial contribution
*/
public final class QueueChannelThrottler extends AbstractMultiRateChannelThrottler {
private final Logger logger = LoggerFactory.getLogger(QueueChannelThrottler.class);
private static final int MAX_QUEUE_LENGTH = 150;
private BlockingQueue<FutureTask<?>> tasks;
private final Rate overallRate;
private final Runnable processQueueTask = () -> {
FutureTask<?> task = tasks.poll();
if (task != null && !task.isCancelled()) {
task.run();
}
};
public QueueChannelThrottler(Rate someRate) {
this(someRate, Executors.newScheduledThreadPool(1), new HashMap<>(), TimeProvider.SYSTEM_PROVIDER,
MAX_QUEUE_LENGTH);
}
public QueueChannelThrottler(Rate someRate, ScheduledExecutorService scheduler) {
this(someRate, scheduler, new HashMap<>(), TimeProvider.SYSTEM_PROVIDER, MAX_QUEUE_LENGTH);
}
public QueueChannelThrottler(Rate someRate, ScheduledExecutorService scheduler, Map<Object, Rate> channels) {
this(someRate, scheduler, channels, TimeProvider.SYSTEM_PROVIDER, MAX_QUEUE_LENGTH);
}
public QueueChannelThrottler(Rate someRate, Map<Object, Rate> channels, int queueLength) {
this(someRate, Executors.newScheduledThreadPool(1), channels, TimeProvider.SYSTEM_PROVIDER, queueLength);
}
public QueueChannelThrottler(Rate someRate, ScheduledExecutorService scheduler, Map<Object, Rate> channels,
TimeProvider timeProvider, int queueLength) {
super(someRate, scheduler, channels, timeProvider);
overallRate = someRate;
tasks = new LinkedBlockingQueue<>(queueLength);
}
@Override
public Future<?> submit(Runnable task) {
return submit(null, task);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Future<?> submit(Object channelKey, Runnable task) {
FutureTask runTask = new FutureTask(task, null);
try {
if (tasks.offer(runTask, overallRate.timeInMillis(), TimeUnit.MILLISECONDS)) {
long throttledTime = channelKey == null ? callTime(null) : callTime(channels.get(channelKey));
long now = timeProvider.getCurrentTimeInMillis();
scheduler.schedule(processQueueTask, throttledTime < now ? 0 : throttledTime - now,
TimeUnit.MILLISECONDS);
return runTask;
} else {
logger.warn("The QueueThrottler can not take the task '{}' at this point in time", runTask.toString());
}
} catch (InterruptedException e) {
logger.error("An exception occurred while scheduling a new taks: '{}'", e.getMessage());
}
return null;
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.concurrent.TimeUnit;
/**
* The {@link Rate} defines a rate limiter that accepts a number of calls to be
* executed in a given time length. If the quota of calls is used, then calls
* are scheduled for the next block of time
*
* @author Karel Goderis - Initial contribution
*/
public final class Rate {
private final int numberCalls;
private final int timeLength;
private final TimeUnit timeUnit;
private final LinkedList<Long> callHistory = new LinkedList<>();
public Rate(int numberCalls, int timeLength, TimeUnit timeUnit) {
this.numberCalls = numberCalls;
this.timeLength = timeLength;
this.timeUnit = timeUnit;
}
public long timeInMillis() {
return timeUnit.toMillis(timeLength);
}
void addCall(long callTime) {
callHistory.addLast(callTime);
}
private void cleanOld(long now) {
ListIterator<Long> i = callHistory.listIterator();
long threshold = now - timeInMillis();
while (i.hasNext()) {
if (i.next() <= threshold) {
i.remove();
} else {
break;
}
}
}
long callTime(long now) {
cleanOld(now);
if (callHistory.size() < numberCalls) {
return now;
}
long lastStart = callHistory.getLast() - timeInMillis();
long firstPeriodCall = lastStart, call;
int count = 0;
Iterator<Long> i = callHistory.descendingIterator();
while (i.hasNext()) {
call = i.next();
if (call < lastStart) {
break;
} else {
count++;
firstPeriodCall = call;
}
}
if (count < numberCalls) {
return firstPeriodCall + 1;
} else {
return firstPeriodCall + timeInMillis() + 1;
}
}
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.throttler;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* The {@link ScheduledChannelThrottler} implements a throttler that maintains a
* single execution rates, and does not maintains order of calls (thus have to
* start from back rather than try to insert things in middle)
*
* @author Karel Goderis - Initial contribution
*/
public final class ScheduledChannelThrottler extends AbstractChannelThrottler {
public ScheduledChannelThrottler(Rate totalRate) {
this(totalRate, Executors.newSingleThreadScheduledExecutor(), new HashMap<>(), TimeProvider.SYSTEM_PROVIDER);
}
public ScheduledChannelThrottler(Rate totalRate, Map<Object, Rate> channels) {
this(totalRate, Executors.newSingleThreadScheduledExecutor(), channels, TimeProvider.SYSTEM_PROVIDER);
}
public ScheduledChannelThrottler(Rate totalRate, ScheduledExecutorService scheduler, Map<Object, Rate> channels,
TimeProvider timeProvider) {
super(totalRate, scheduler, channels, timeProvider);
}
public void submitSync(Object channelKey, Runnable task) throws InterruptedException {
Thread.sleep(getThrottleDelay(channelKey));
task.run();
}
public void submitSync(Runnable task) throws InterruptedException {
long delay = callTime(null) - timeProvider.getCurrentTimeInMillis();
Thread.sleep(getThrottleDelay(delay));
task.run();
}
@Override
public Future<?> submit(Runnable task) {
long delay = callTime(null) - timeProvider.getCurrentTimeInMillis();
return scheduler.schedule(task, delay < 0 ? 0 : delay, TimeUnit.MILLISECONDS);
}
@Override
public Future<?> submit(Object channelKey, Runnable task) {
return scheduler.schedule(task, getThrottleDelay(channelKey), TimeUnit.MILLISECONDS);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 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.throttler;
/**
* The {@link TimeProvider} provides time stamps
*
* @author Karel Goderis - Initial contribution
*/
public interface TimeProvider {
public static final TimeProvider SYSTEM_PROVIDER = new TimeProvider() {
@Override
public long getCurrentTimeInMillis() {
return System.currentTimeMillis();
}
};
public long getCurrentTimeInMillis();
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="tesla" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Tesla Binding</name>
<description>This is the binding for Tesla Electric Vehicles.</description>
<author>Karel Goderis</author>
</binding:binding>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="account">
<label>Tesla Account</label>
<description>Tesla Owner Account</description>
<config-description>
<parameter name="refreshToken" type="text" required="false">
<label>Refresh Token</label>
<description>Refresh token for account authentication. Use "smarthome:tesla" to retrieve it.</description>
</parameter>
<parameter name="username" type="text" required="false">
<advanced>true</advanced>
<label>Username</label>
<description>Username for the Tesla Remote Service, e.g email address.</description>
</parameter>
<parameter name="password" type="text" required="false">
<advanced>true</advanced>
<context>password</context>
<label>Password</label>
<description>Password for the Tesla Remote Service</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,648 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<channel-type id="autoconditioning">
<item-type>Switch</item-type>
<label>Auto Conditioning</label>
<description>Turns on auto-conditioning (a/c or heating)</description>
</channel-type>
<channel-type id="autoparkstate" advanced="true">
<item-type>String</item-type>
<label>Autopark State</label>
<description>Undocumented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="autoparkstyle" advanced="true">
<item-type>String</item-type>
<label>Autopark Style</label>
<description>Undocumented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="batterycurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Battery Current</label>
<description>Current (Ampere) floating into (+) or out (-) of the battery</description>
<state pattern="%.1f A" readOnly="true"></state>
</channel-type>
<channel-type id="batteryheater" advanced="true">
<item-type>Switch</item-type>
<label>Battery Heater</label>
<description>Indicates if the battery heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="batteryheaternopower" advanced="true">
<item-type>Switch</item-type>
<label>Battery Heater Power</label>
<description>Indicates if there is enough power to use the battery heater</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="batterylevel">
<item-type>Number</item-type>
<label>Battery Level</label>
<description>State of the battery in %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="batteryrange" advanced="true">
<item-type>Number:Length</item-type>
<label>Battery Range</label>
<description>Range of the battery</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="calendarenabled" advanced="true">
<item-type>Switch</item-type>
<label>Calendar Enabled</label>
<description>Indicates if access to a remote calendar is enabled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="centerdisplay" advanced="true">
<item-type>Switch</item-type>
<label>Central Display State</label>
<description>Indicates the state of the central display in the vehicle</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="centerrearseatheater" advanced="true">
<item-type>Switch</item-type>
<label>Center Rear Seat Heater</label>
<description>Indicates if the center rear seat heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="charge" advanced="true">
<item-type>Switch</item-type>
<label>Charge</label>
<description>Start (ON) or stop (OFF) charging</description>
</channel-type>
<channel-type id="chargecable" advanced="true">
<item-type>String</item-type>
<label>Charge Cable</label>
<description>Undocumented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="chargecurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Charge Current</label>
<description>Current (Ampere) requested from the charger</description>
<state pattern="%.1f A" readOnly="true"></state>
</channel-type>
<channel-type id="chargeenablerequest" advanced="true">
<item-type>Switch</item-type>
<label>Charge Enable Request</label>
<description>Undocumented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="chargeenergyadded" advanced="true">
<item-type>Number:Energy</item-type>
<label>Charge Energy Added</label>
<description>Energy added, in kWh, during the last charging session</description>
<state pattern="%d kWh" readOnly="true"></state>
</channel-type>
<channel-type id="chargelimit" advanced="true">
<item-type>Dimmer</item-type>
<label>Charge Limit</label>
<description>Limit charging of the vehicle to the given %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="chargelimitmaximum" advanced="true">
<item-type>Dimmer</item-type>
<label>Charge Limit Maximum</label>
<description>Maximum charging limit of the vehicle, as %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="chargelimitminimum" advanced="true">
<item-type>Dimmer</item-type>
<label>Charge Limit Minimum</label>
<description>Minimum charging limit of the vehicle, as %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="chargelimitsocstandard" advanced="true">
<item-type>Dimmer</item-type>
<label>Charge Limit SOC Standard</label>
<description>Standard charging limity of the vehicle, in %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="chargeidealdistanceadded" advanced="true">
<item-type>Number:Length</item-type>
<label>"Ideal" Charge Distance Added</label>
<description>"Ideal" range added during the last charging session</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="chargemaxcurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Charge Max Current</label>
<description>Maximum current (Ampere) that can be requested from the charger</description>
<state pattern="%.1f A" readOnly="true"></state>
</channel-type>
<channel-type id="chargerateddistanceadded" advanced="true">
<item-type>Number:Length</item-type>
<label>"Rated" Charge Distance Added</label>
<description>"Rated" range added during the last charging session</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="chargerate" advanced="true">
<item-type>Number:Speed</item-type>
<label>Charge Rate</label>
<description>Distance per hour charging rate</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="chargestartingrange" advanced="true">
<item-type>String</item-type>
<label>Charge Starting Range</label>
<description>Undocumented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="chargestartingsoc" advanced="true">
<item-type>String</item-type>
<label>Charge Starting SOC</label>
<description>Undocumented / To be defined</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="chargingstate">
<item-type>String</item-type>
<label>Charging State</label>
<description>“Starting”, “Complete”, “Charging”, “Disconnected”, “Stopped”, “NoPower”</description>
<state readOnly="true">
<options>
<option value="Starting">Starting</option>
<option value="Complete">Complete</option>
<option value="Charging">Charging</option>
<option value="Disconnected">Disconnected</option>
<option value="Stopped">Stopped</option>
<option value="NoPower">No Power</option>
</options>
</state>
</channel-type>
<channel-type id="chargetomax" advanced="true">
<item-type>Switch</item-type>
<label>Charge To Max Range</label>
<description>Indicates if the charging to the maximum range is enabled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="chargeport">
<item-type>Switch</item-type>
<label>Charge Port</label>
<description>Open the Charge Port (ON) or indicates the state of the Charge Port (ON/OFF if Open/Closed)</description>
</channel-type>
<channel-type id="chargercurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Charge Current</label>
<description>Current (Ampere) actually being drawn from the charger</description>
<state pattern="%.1f A" readOnly="true"></state>
</channel-type>
<channel-type id="chargerphases" advanced="true">
<item-type>Number</item-type>
<label>Charger Phases</label>
<description>Indicates the number of phases (1 to 3) used for charging</description>
<state pattern="%d" readOnly="true"></state>
</channel-type>
<channel-type id="chargermaxcurrent" advanced="true">
<item-type>Number:ElectricCurrent</item-type>
<label>Charger Maximum Current</label>
<description>Maximum current (Ampere) that can be delivered by the charger</description>
<state pattern="%.1f A" readOnly="true"></state>
</channel-type>
<channel-type id="chargerpower" advanced="true">
<item-type>Number</item-type>
<label>Charger Power</label>
<description>Power actually delivered by the charger</description>
<state pattern="%.1f kW" readOnly="true"></state>
</channel-type>
<channel-type id="chargervoltage" advanced="true">
<item-type>Number:ElectricPotential</item-type>
<label>Charger Voltage</label>
<description>Voltage (V) actually presented by the charger</description>
<state pattern="%.1f V" readOnly="true"></state>
</channel-type>
<channel-type id="climate">
<item-type>Switch</item-type>
<label>Climate</label>
<description>Climate status indicator</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="driverfrontdoor" advanced="true">
<item-type>Contact</item-type>
<label>Driver Front Door</label>
<description>Indicates if the front door at the driver's side is opened</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="doorlock">
<item-type>Switch</item-type>
<label>Door Lock</label>
<description>Lock or unlock the car</description>
</channel-type>
<channel-type id="driverreardoor" advanced="true">
<item-type>Contact</item-type>
<label>Driver Rear Door</label>
<description>Indicates if the rear door at the driver's side is opened</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="drivertemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Driver Temperature</label>
<description>Indicates the auto conditioning temperature set at the driver's side</description>
<state pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="eventstamp" advanced="true">
<item-type>DateTime</item-type>
<label>Event Timestamp</label>
<description>Timestamp of the last event received from the Tesla streaming service</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="estimatedbatteryrange" advanced="true">
<item-type>Number:Length</item-type>
<label>Estimated Battery Range</label>
<description>Estimated battery range</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="estimatedrange" advanced="true">
<item-type>Number</item-type>
<label>Estimated Range</label>
<description>Estimated range of the vehicle</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fan" advanced="true">
<item-type>Number</item-type>
<label>Fan</label>
<description>Indicates the speed (0-7) of the fan</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="flashlights" advanced="true">
<item-type>Switch</item-type>
<label>Flash Lights</label>
<description>Flash the lights of the car (when ON is received)</description>
</channel-type>
<channel-type id="frontdefroster" advanced="true">
<item-type>Switch</item-type>
<label>Front Defroster</label>
<description>Indicates if the front defroster is enabled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fronttrunk" advanced="true">
<item-type>Switch</item-type>
<label>Front Trunk</label>
<description>Indicates if the front trunk is opened, or open the front trunk when ON is received</description>
</channel-type>
<channel-type id="gpstimestamp" advanced="true">
<item-type>DateTime</item-type>
<label>GPS Time Stamp</label>
<description>Time stamp of the most recent GPS location of the vehicle</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="heading" advanced="true">
<item-type>Number:Angle</item-type>
<label>Heading</label>
<description>Indicates the (compass) heading of the car, in 0-360 degrees</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="headingestimation" advanced="true">
<item-type>Number:Angle</item-type>
<label>Estimated Heading</label>
<description>Estimated (compass) heading of the car, in 0 to 360 degrees</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="honkhorn" advanced="true">
<item-type>Switch</item-type>
<label>Honk the Horn</label>
<description>Honk the horn of the vehicle, when ON is received</description>
</channel-type>
<channel-type id="homelink" advanced="true">
<item-type>Switch</item-type>
<label>Homelink Nearby</label>
<description>Indicates if the Home Link is nearby</description>
</channel-type>
<channel-type id="idealbatteryrange" advanced="true">
<item-type>Number:Length</item-type>
<label>Ideal Battery Range</label>
<description>Indicates the Batter Range</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="insidetemp">
<item-type>Number:Temperature</item-type>
<label>Inside Temperature</label>
<description>Indicates the inside temperature of the vehicle</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="lefttempdirection" advanced="true">
<item-type>Number</item-type>
<label>Left Temperature Direction</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="lastautoparkerror" advanced="true">
<item-type>String</item-type>
<label>Last Autopark Error</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="location" advanced="false">
<item-type>Location</item-type>
<label>Location</label>
<description>The actual position of the vehicle</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="leftseatheater" advanced="true">
<item-type>Switch</item-type>
<label>Left Seat Heater</label>
<description>Indicates if the left seat heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="leftrearseatheater" advanced="true">
<item-type>Switch</item-type>
<label>Left Rear Seat Heater</label>
<description>Indicates if the left rear seat heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="leftrearbackseatheater" advanced="true">
<item-type>Number</item-type>
<label>Left Rear Backseat Heater</label>
<description>Indicates the level (0,1,2 or 3) of the left rear backseat heater</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="managedcharging" advanced="true">
<item-type>Switch</item-type>
<label>Managed Charging</label>
<description>Indicates managed charging is active</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="managedchargingcancelled" advanced="true">
<item-type>Switch</item-type>
<label>Managed Charging Cancelled</label>
<description>Indicates managed charging is cancelled by the user</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="managedchargingstart" advanced="true">
<item-type>String</item-type>
<label>Managed Charging Start Time</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="maxcharges" advanced="true">
<item-type>Number</item-type>
<label>Max Charges</label>
<description>Indicates the number of consecutive "Max Range Charges" performed by the vehicle</description>
<state pattern="%d" readOnly="true"></state>
</channel-type>
<channel-type id="minavailabletemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Minimum Temperature</label>
<description>Indicates the minimal inside temperature of the vehicle</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="maxavailabletemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Maximum Temperature</label>
<description>Indicates the maximum inside temperature of the vehicle</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="mobileenabled" advanced="true">
<item-type>Switch</item-type>
<label>Mobile Enabled</label>
<description>Indicates whether the vehicle can be remotely controlled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="notenoughpower" advanced="true">
<item-type>Switch</item-type>
<label>Not Enought Power </label>
<description>Indicates if not enough power (ON) is available to heat the vehicle</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="notificationsenabled" advanced="true">
<item-type>Switch</item-type>
<label>Notifications Enabled</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="notificationssupported" advanced="true">
<item-type>Switch</item-type>
<label>Notifications Supported</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="odometer">
<item-type>Number:Length</item-type>
<label>Odometer</label>
<description>Odometer of the vehicle</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="outsidetemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Outside Temperature</label>
<description>Indicates the outside temperature of the vehicle</description>
<state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="parsedcalendar" advanced="true">
<item-type>Switch</item-type>
<label>Parsed Calendar Supported</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="passengertemp" advanced="true">
<item-type>Number</item-type>
<label>Passenger Temperature</label>
<description>Indicates the auto conditioning temperature set at the passenger's side</description>
<state pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="passengerfrontdoor" advanced="true">
<item-type>Contact</item-type>
<label>Passenger Front Door</label>
<description>Indicates if the front door at the passenger's side is opened</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="passengerreardoor" advanced="true">
<item-type>Contact</item-type>
<label>Passenger Rear Door</label>
<description>Indicates if the rear door at the passenger's side is opened</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="power" advanced="true">
<item-type>Number</item-type>
<label>Power</label>
<description>Net kW flowing in (+) or out (-) of the battery</description>
<state pattern="%.1f kW" readOnly="true"></state>
</channel-type>
<channel-type id="preconditioning" advanced="true">
<item-type>Switch</item-type>
<label>Preconditioning</label>
<description>Indicates if preconditioning is activated</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="range" advanced="true">
<item-type>Number</item-type>
<label>Range</label>
<description>Vehicle range - Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="reardefroster" advanced="true">
<item-type>Switch</item-type>
<label>Rear Defroster</label>
<description>Indicates if the rear defroster is enabled</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="remotestartenabled" advanced="true">
<item-type>Switch</item-type>
<label>Remote Start</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="reartrunk" advanced="true">
<item-type>Switch</item-type>
<label>Rear Trunk</label>
<description>Indicates if the rear trunk is opened, or open/close the rear trunk when ON/OFF is received</description>
</channel-type>
<channel-type id="remotestart" advanced="true">
<item-type>Switch</item-type>
<label>Remote Start</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="remotestartsupported" advanced="true">
<item-type>Switch</item-type>
<label>Remote Start Supported</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="rightseatheater" advanced="true">
<item-type>Switch</item-type>
<label>Right Seat Heater</label>
<description>Indicates if the right seat heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="rightrearseatheater" advanced="true">
<item-type>Switch</item-type>
<label>Right Rear Seat Heater</label>
<description>Indicates if the right rear seat heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="rightrearbackseatheater" advanced="true">
<item-type>Number</item-type>
<label>Right Rear Backseat Heater</label>
<description>Indicates the level (0,1,2 or 3) of the right rear backseat heater</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="righttempdirection" advanced="true">
<item-type>Number</item-type>
<label>Right Temperature Direction</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="scheduledchargingpending" advanced="true">
<item-type>Switch</item-type>
<label>Scheduled Charging Pending</label>
<description>Indicates if a scheduled charging session is still pending</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="scheduledchargingstart" advanced="true">
<item-type>DateTime</item-type>
<label>Scheduled Charging Start</label>
<description>Indicates when the scheduled charging session will start, in yyyy-MM-dd'T'HH:mm:ss format</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="shiftstate" advanced="true">
<item-type>String</item-type>
<label>Shift State</label>
<description>Indicates the state of the transmission, “P”, “D”, “R”, or “N”</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="sidemirrorheaters" advanced="true">
<item-type>Switch</item-type>
<label>Side Mirror Heaters</label>
<description>Indicates if the side mirror heaters are switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="smartpreconditioning" advanced="true">
<item-type>Switch</item-type>
<label>Smart Preconditioning</label>
<description>Indicates if smart preconditioning is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="soc" advanced="true">
<item-type>Number</item-type>
<label>State of Charge</label>
<description>State of Charge, in %</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="speed">
<item-type>Number:Speed</item-type>
<label>Speed</label>
<description>Vehicle speed</description>
<state pattern="%d %unit%" readOnly="true"></state>
</channel-type>
<channel-type id="state" advanced="true">
<item-type>String</item-type>
<label>State</label>
<description>“online”, “asleep”, “waking”</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="steeringwheelheater" advanced="true">
<item-type>Switch</item-type>
<label>Steering Wheel Heater</label>
<description>Indicates if the steering wheel heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="sunroofstate" advanced="true">
<item-type>String</item-type>
<label>Sunroof State</label>
<description>“unknown”, “open”, “closed”, “vent”, “comfort”</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="sunroof">
<item-type>Dimmer</item-type>
<label>Sunroof</label>
<description>Open or close the sunroof to provided % (0 closed, 100 fully open)</description>
<state pattern="%d %%"></state>
</channel-type>
<channel-type id="combinedtemp" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Combined Temperature</label>
<description>Combined average temperature of the driver and passenger autoconditioning settings. The temperature for
the driver and passenger will be synced when set.</description>
<state pattern="%.1f %unit%"></state>
</channel-type>
<channel-type id="timetofullcharge" advanced="true">
<item-type>Number</item-type>
<label>Time To Full Charge</label>
<description>Number of hours to fully charge the battery</description>
<state pattern="%.1f h" readOnly="true"></state>
</channel-type>
<channel-type id="tripcharging" advanced="true">
<item-type>Switch</item-type>
<label>Trip Charging</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="usablebatterylevel" advanced="true">
<item-type>Number</item-type>
<label>Usable Battery Level</label>
<description>Indicates the % of battery that can be used for vehicle functions like driving</description>
<state pattern="%.1f %%" readOnly="true"></state>
</channel-type>
<channel-type id="userchargeenablerequest" advanced="true">
<item-type>String</item-type>
<label>User Charge Enable Request</label>
<description>Not documented / To be defined</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="valetmode" advanced="true">
<item-type>Switch</item-type>
<label>Valet Mode</label>
<description>Enable or disable Valet Mode</description>
</channel-type>
<channel-type id="valetpin" advanced="true">
<item-type>Switch</item-type>
<label>Valet PIN Required</label>
<description>Indicates if a PIN code is required to disable valet mode</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="wakeup" advanced="true">
<item-type>Switch</item-type>
<label>Wake Up</label>
<description>Wake up the vehicle from a (deep) sleep</description>
</channel-type>
<channel-type id="wiperbladeheater" advanced="true">
<item-type>Switch</item-type>
<label>Wiperblade Heater</label>
<description>Indicates if the wiperblade heater is switched on</description>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="model3">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>Tesla Model 3</label>
<description>A Tesla Model 3 Vehicle</description>
<channels>
<channel id="autoconditioning" typeId="autoconditioning"/>
<channel id="autoparkstate" typeId="autoparkstate"/>
<channel id="autoparkstate2" typeId="autoparkstate"/>
<channel id="autoparkstyle" typeId="autoparkstyle"/>
<channel id="batterycurrent" typeId="batterycurrent"/>
<channel id="batterylevel" typeId="batterylevel"/>
<channel id="batteryrange" typeId="batteryrange"/>
<channel id="calendarenabled" typeId="calendarenabled"/>
<channel id="centerdisplay" typeId="centerdisplay"/>
<channel id="centerrearseatheater" typeId="centerrearseatheater"/>
<channel id="charge" typeId="charge"/>
<channel id="chargecable" typeId="chargecable"/>
<channel id="chargecurrent" typeId="chargecurrent"/>
<channel id="chargeenablerequest" typeId="chargeenablerequest"/>
<channel id="chargeenergyadded" typeId="chargeenergyadded"/>
<channel id="chargeidealdistanceadded" typeId="chargeidealdistanceadded"/>
<channel id="chargelimit" typeId="chargelimit"/>
<channel id="chargelimitmaximum" typeId="chargelimitmaximum"/>
<channel id="chargelimitminimum" typeId="chargelimitminimum"/>
<channel id="chargelimitsocstandard" typeId="chargelimitsocstandard"/>
<channel id="chargemaxcurrent" typeId="chargemaxcurrent"/>
<channel id="chargeport" typeId="chargeport"/>
<channel id="chargerate" typeId="chargerate"/>
<channel id="chargerateddistanceadded" typeId="chargerateddistanceadded"/>
<channel id="chargercurrent" typeId="chargercurrent"/>
<channel id="chargermaxcurrent" typeId="chargermaxcurrent"/>
<channel id="chargerphases" typeId="chargerphases"/>
<channel id="chargerpower" typeId="chargerpower"/>
<channel id="chargervoltage" typeId="chargervoltage"/>
<channel id="chargestartingrange" typeId="chargestartingrange"/>
<channel id="chargestartingsoc" typeId="chargestartingsoc"/>
<channel id="chargetomax" typeId="chargetomax"/>
<channel id="chargingstate" typeId="chargingstate"/>
<channel id="climate" typeId="climate"/>
<channel id="doorlock" typeId="doorlock"/>
<channel id="driverfrontdoor" typeId="driverfrontdoor"/>
<channel id="driverreardoor" typeId="driverreardoor"/>
<channel id="drivertemp" typeId="drivertemp"/>
<channel id="estimatedbatteryrange" typeId="estimatedbatteryrange"/>
<channel id="estimatedrange" typeId="estimatedrange"/>
<channel id="eventstamp" typeId="eventstamp"/>
<channel id="fan" typeId="fan"/>
<channel id="flashlights" typeId="flashlights"/>
<channel id="frontdefroster" typeId="frontdefroster"/>
<channel id="fronttrunk" typeId="fronttrunk"/>
<channel id="gpstimestamp" typeId="gpstimestamp"/>
<channel id="heading" typeId="heading"/>
<channel id="headingestimation" typeId="headingestimation"/>
<channel id="homelink" typeId="homelink"/>
<channel id="honkhorn" typeId="honkhorn"/>
<channel id="idealbatteryrange" typeId="idealbatteryrange"/>
<channel id="insidetemp" typeId="insidetemp"/>
<channel id="leftrearbackseatheater" typeId="leftrearbackseatheater"/>
<channel id="leftrearseatheater" typeId="leftrearseatheater"/>
<channel id="leftseatheater" typeId="leftseatheater"/>
<channel id="lefttempdirection" typeId="lefttempdirection"/>
<channel id="location" typeId="location"/>
<channel id="managedcharging" typeId="managedcharging"/>
<channel id="managedchargingcancelled" typeId="managedchargingcancelled"/>
<channel id="managedchargingstart" typeId="managedchargingstart"/>
<channel id="maxavailabletemp" typeId="maxavailabletemp"/>
<channel id="maxcharges" typeId="maxcharges"/>
<channel id="mobileenabled" typeId="mobileenabled"/>
<channel id="minavailabletemp" typeId="minavailabletemp"/>
<channel id="notenoughpower" typeId="notenoughpower"/>
<channel id="notificationsenabled" typeId="notificationsenabled"/>
<channel id="notificationssupported" typeId="notificationssupported"/>
<channel id="odometer" typeId="odometer"/>
<channel id="outsidetemp" typeId="outsidetemp"/>
<channel id="parsedcalendar" typeId="parsedcalendar"/>
<channel id="passengerfrontdoor" typeId="passengerfrontdoor"/>
<channel id="passengerreardoor" typeId="passengerreardoor"/>
<channel id="passengertemp" typeId="passengertemp"/>
<channel id="power" typeId="power"/>
<channel id="preconditioning" typeId="preconditioning"/>
<channel id="range" typeId="range"/>
<channel id="reardefroster" typeId="reardefroster"/>
<channel id="reartrunk" typeId="reartrunk"/>
<channel id="remotestart" typeId="remotestart"/>
<channel id="remotestartenabled" typeId="remotestartenabled"/>
<channel id="remotestartsupported" typeId="remotestartsupported"/>
<channel id="rightrearbackseatheater" typeId="rightrearbackseatheater"/>
<channel id="rightrearseatheater" typeId="rightrearseatheater"/>
<channel id="rightseatheater" typeId="rightseatheater"/>
<channel id="scheduledchargingpending" typeId="scheduledchargingpending"/>
<channel id="scheduledchargingstart" typeId="scheduledchargingstart"/>
<channel id="shiftstate" typeId="shiftstate"/>
<channel id="sidemirrorheaters" typeId="sidemirrorheaters"/>
<channel id="smartpreconditioning" typeId="smartpreconditioning"/>
<channel id="soc" typeId="soc"/>
<channel id="speed" typeId="speed"/>
<channel id="state" typeId="state"/>
<channel id="combinedtemp" typeId="combinedtemp"/>
<channel id="timetofullcharge" typeId="timetofullcharge"/>
<channel id="tripcharging" typeId="tripcharging"/>
<channel id="usablebatterylevel" typeId="usablebatterylevel"/>
<channel id="userchargeenablerequest" typeId="userchargeenablerequest"/>
<channel id="valetmode" typeId="valetmode"/>
<channel id="valetpin" typeId="valetpin"/>
<channel id="wakeup" typeId="wakeup"/>
</channels>
<config-description>
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number</label>
<description>VIN of the vehicle</description>
</parameter>
<parameter name="valetpin" type="integer" min="0" max="9999" required="false">
<context>password</context>
<label>Valet PIN</label>
<description>PIN to use when enabling Valet Mode</description>
</parameter>
<parameter name="allowWakeup" type="boolean" required="false">
<default>false</default>
<label>Allow Wake-Up</label>
<description>Allows waking up the vehicle. Caution: This can result in huge vampire drain!</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="models">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>Tesla Model S</label>
<description>A Tesla Model S Vehicle</description>
<channels>
<channel id="autoconditioning" typeId="autoconditioning"/>
<channel id="autoparkstate" typeId="autoparkstate"/>
<channel id="autoparkstate2" typeId="autoparkstate"/>
<channel id="autoparkstyle" typeId="autoparkstyle"/>
<channel id="batterycurrent" typeId="batterycurrent"/>
<channel id="batteryheater" typeId="batteryheater"/>
<channel id="batteryheaternopower" typeId="batteryheaternopower"/>
<channel id="batterylevel" typeId="batterylevel"/>
<channel id="batteryrange" typeId="batteryrange"/>
<channel id="calendarenabled" typeId="calendarenabled"/>
<channel id="centerdisplay" typeId="centerdisplay"/>
<channel id="centerrearseatheater" typeId="centerrearseatheater"/>
<channel id="charge" typeId="charge"/>
<channel id="chargecable" typeId="chargecable"/>
<channel id="chargecurrent" typeId="chargecurrent"/>
<channel id="chargeenablerequest" typeId="chargeenablerequest"/>
<channel id="chargeenergyadded" typeId="chargeenergyadded"/>
<channel id="chargeidealdistanceadded" typeId="chargeidealdistanceadded"/>
<channel id="chargelimit" typeId="chargelimit"/>
<channel id="chargelimitmaximum" typeId="chargelimitmaximum"/>
<channel id="chargelimitminimum" typeId="chargelimitminimum"/>
<channel id="chargelimitsocstandard" typeId="chargelimitsocstandard"/>
<channel id="chargemaxcurrent" typeId="chargemaxcurrent"/>
<channel id="chargeport" typeId="chargeport"/>
<channel id="chargerate" typeId="chargerate"/>
<channel id="chargerateddistanceadded" typeId="chargerateddistanceadded"/>
<channel id="chargercurrent" typeId="chargercurrent"/>
<channel id="chargermaxcurrent" typeId="chargermaxcurrent"/>
<channel id="chargerphases" typeId="chargerphases"/>
<channel id="chargerpower" typeId="chargerpower"/>
<channel id="chargervoltage" typeId="chargervoltage"/>
<channel id="chargestartingrange" typeId="chargestartingrange"/>
<channel id="chargestartingsoc" typeId="chargestartingsoc"/>
<channel id="chargetomax" typeId="chargetomax"/>
<channel id="chargingstate" typeId="chargingstate"/>
<channel id="climate" typeId="climate"/>
<channel id="doorlock" typeId="doorlock"/>
<channel id="driverfrontdoor" typeId="driverfrontdoor"/>
<channel id="driverreardoor" typeId="driverreardoor"/>
<channel id="drivertemp" typeId="drivertemp"/>
<channel id="estimatedbatteryrange" typeId="estimatedbatteryrange"/>
<channel id="estimatedrange" typeId="estimatedrange"/>
<channel id="eventstamp" typeId="eventstamp"/>
<channel id="fan" typeId="fan"/>
<channel id="flashlights" typeId="flashlights"/>
<channel id="frontdefroster" typeId="frontdefroster"/>
<channel id="fronttrunk" typeId="fronttrunk"/>
<channel id="gpstimestamp" typeId="gpstimestamp"/>
<channel id="heading" typeId="heading"/>
<channel id="headingestimation" typeId="headingestimation"/>
<channel id="homelink" typeId="homelink"/>
<channel id="honkhorn" typeId="honkhorn"/>
<channel id="idealbatteryrange" typeId="idealbatteryrange"/>
<channel id="insidetemp" typeId="insidetemp"/>
<channel id="leftrearbackseatheater" typeId="leftrearbackseatheater"/>
<channel id="leftrearseatheater" typeId="leftrearseatheater"/>
<channel id="leftseatheater" typeId="leftseatheater"/>
<channel id="lefttempdirection" typeId="lefttempdirection"/>
<channel id="location" typeId="location"/>
<channel id="managedcharging" typeId="managedcharging"/>
<channel id="managedchargingcancelled" typeId="managedchargingcancelled"/>
<channel id="managedchargingstart" typeId="managedchargingstart"/>
<channel id="maxavailabletemp" typeId="maxavailabletemp"/>
<channel id="maxcharges" typeId="maxcharges"/>
<channel id="mobileenabled" typeId="mobileenabled"/>
<channel id="minavailabletemp" typeId="minavailabletemp"/>
<channel id="nativelocation" typeId="location"/>
<channel id="notenoughpower" typeId="notenoughpower"/>
<channel id="notificationsenabled" typeId="notificationsenabled"/>
<channel id="notificationssupported" typeId="notificationssupported"/>
<channel id="odometer" typeId="odometer"/>
<channel id="outsidetemp" typeId="outsidetemp"/>
<channel id="parsedcalendar" typeId="parsedcalendar"/>
<channel id="passengerfrontdoor" typeId="passengerfrontdoor"/>
<channel id="passengerreardoor" typeId="passengerreardoor"/>
<channel id="passengertemp" typeId="passengertemp"/>
<channel id="power" typeId="power"/>
<channel id="preconditioning" typeId="preconditioning"/>
<channel id="range" typeId="range"/>
<channel id="reardefroster" typeId="reardefroster"/>
<channel id="reartrunk" typeId="reartrunk"/>
<channel id="remotestart" typeId="remotestart"/>
<channel id="remotestartenabled" typeId="remotestartenabled"/>
<channel id="remotestartsupported" typeId="remotestartsupported"/>
<channel id="rightrearbackseatheater" typeId="rightrearbackseatheater"/>
<channel id="rightrearseatheater" typeId="rightrearseatheater"/>
<channel id="rightseatheater" typeId="rightseatheater"/>
<channel id="scheduledchargingpending" typeId="scheduledchargingpending"/>
<channel id="scheduledchargingstart" typeId="scheduledchargingstart"/>
<channel id="shiftstate" typeId="shiftstate"/>
<channel id="sidemirrorheaters" typeId="sidemirrorheaters"/>
<channel id="smartpreconditioning" typeId="smartpreconditioning"/>
<channel id="soc" typeId="soc"/>
<channel id="speed" typeId="speed"/>
<channel id="state" typeId="state"/>
<channel id="steeringwheelheater" typeId="steeringwheelheater"/>
<channel id="sunroof" typeId="sunroof"/>
<channel id="sunroofstate" typeId="sunroofstate"/>
<channel id="combinedtemp" typeId="combinedtemp"/>
<channel id="timetofullcharge" typeId="timetofullcharge"/>
<channel id="tripcharging" typeId="tripcharging"/>
<channel id="usablebatterylevel" typeId="usablebatterylevel"/>
<channel id="userchargeenablerequest" typeId="userchargeenablerequest"/>
<channel id="valetmode" typeId="valetmode"/>
<channel id="valetpin" typeId="valetpin"/>
<channel id="wakeup" typeId="wakeup"/>
<channel id="wiperbladeheater" typeId="wiperbladeheater"/>
</channels>
<config-description>
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number</label>
<description>VIN of the vehicle</description>
</parameter>
<parameter name="valetpin" type="integer" min="0" max="9999" required="false">
<context>password</context>
<label>Valet PIN</label>
<description>PIN to use when enabling Valet Mode</description>
</parameter>
<parameter name="allowWakeup" type="boolean" required="false">
<default>false</default>
<label>Allow Wake-Up</label>
<description>Allows waking up the vehicle. Caution: This can result in huge vampire drain!</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="modelx">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>Tesla Model X</label>
<description>A Tesla Model X Vehicle</description>
<channels>
<channel id="autoconditioning" typeId="autoconditioning"/>
<channel id="autoparkstate" typeId="autoparkstate"/>
<channel id="autoparkstate2" typeId="autoparkstate"/>
<channel id="autoparkstyle" typeId="autoparkstyle"/>
<channel id="batterycurrent" typeId="batterycurrent"/>
<channel id="batteryheater" typeId="batteryheater"/>
<channel id="batteryheaternopower" typeId="batteryheaternopower"/>
<channel id="batterylevel" typeId="batterylevel"/>
<channel id="batteryrange" typeId="batteryrange"/>
<channel id="calendarenabled" typeId="calendarenabled"/>
<channel id="centerdisplay" typeId="centerdisplay"/>
<channel id="centerrearseatheater" typeId="centerrearseatheater"/>
<channel id="charge" typeId="charge"/>
<channel id="chargecable" typeId="chargecable"/>
<channel id="chargecurrent" typeId="chargecurrent"/>
<channel id="chargeenablerequest" typeId="chargeenablerequest"/>
<channel id="chargeenergyadded" typeId="chargeenergyadded"/>
<channel id="chargeidealdistanceadded" typeId="chargeidealdistanceadded"/>
<channel id="chargelimit" typeId="chargelimit"/>
<channel id="chargelimitmaximum" typeId="chargelimitmaximum"/>
<channel id="chargelimitminimum" typeId="chargelimitminimum"/>
<channel id="chargelimitsocstandard" typeId="chargelimitsocstandard"/>
<channel id="chargemaxcurrent" typeId="chargemaxcurrent"/>
<channel id="chargeport" typeId="chargeport"/>
<channel id="chargerate" typeId="chargerate"/>
<channel id="chargerateddistanceadded" typeId="chargerateddistanceadded"/>
<channel id="chargercurrent" typeId="chargercurrent"/>
<channel id="chargermaxcurrent" typeId="chargermaxcurrent"/>
<channel id="chargerphases" typeId="chargerphases"/>
<channel id="chargerpower" typeId="chargerpower"/>
<channel id="chargervoltage" typeId="chargervoltage"/>
<channel id="chargestartingrange" typeId="chargestartingrange"/>
<channel id="chargestartingsoc" typeId="chargestartingsoc"/>
<channel id="chargetomax" typeId="chargetomax"/>
<channel id="chargingstate" typeId="chargingstate"/>
<channel id="climate" typeId="climate"/>
<channel id="doorlock" typeId="doorlock"/>
<channel id="driverfrontdoor" typeId="driverfrontdoor"/>
<channel id="driverreardoor" typeId="driverreardoor"/>
<channel id="drivertemp" typeId="drivertemp"/>
<channel id="estimatedbatteryrange" typeId="estimatedbatteryrange"/>
<channel id="estimatedrange" typeId="estimatedrange"/>
<channel id="eventstamp" typeId="eventstamp"/>
<channel id="fan" typeId="fan"/>
<channel id="flashlights" typeId="flashlights"/>
<channel id="frontdefroster" typeId="frontdefroster"/>
<channel id="fronttrunk" typeId="fronttrunk"/>
<channel id="gpstimestamp" typeId="gpstimestamp"/>
<channel id="heading" typeId="heading"/>
<channel id="headingestimation" typeId="headingestimation"/>
<channel id="homelink" typeId="homelink"/>
<channel id="honkhorn" typeId="honkhorn"/>
<channel id="idealbatteryrange" typeId="idealbatteryrange"/>
<channel id="insidetemp" typeId="insidetemp"/>
<channel id="leftrearbackseatheater" typeId="leftrearbackseatheater"/>
<channel id="leftrearseatheater" typeId="leftrearseatheater"/>
<channel id="leftseatheater" typeId="leftseatheater"/>
<channel id="lefttempdirection" typeId="lefttempdirection"/>
<channel id="location" typeId="location"/>
<channel id="managedcharging" typeId="managedcharging"/>
<channel id="managedchargingcancelled" typeId="managedchargingcancelled"/>
<channel id="managedchargingstart" typeId="managedchargingstart"/>
<channel id="maxavailabletemp" typeId="maxavailabletemp"/>
<channel id="maxcharges" typeId="maxcharges"/>
<channel id="mobileenabled" typeId="mobileenabled"/>
<channel id="minavailabletemp" typeId="minavailabletemp"/>
<channel id="nativelocation" typeId="location"/>
<channel id="notenoughpower" typeId="notenoughpower"/>
<channel id="notificationsenabled" typeId="notificationsenabled"/>
<channel id="notificationssupported" typeId="notificationssupported"/>
<channel id="odometer" typeId="odometer"/>
<channel id="outsidetemp" typeId="outsidetemp"/>
<channel id="parsedcalendar" typeId="parsedcalendar"/>
<channel id="passengerfrontdoor" typeId="passengerfrontdoor"/>
<channel id="passengerreardoor" typeId="passengerreardoor"/>
<channel id="passengertemp" typeId="passengertemp"/>
<channel id="power" typeId="power"/>
<channel id="preconditioning" typeId="preconditioning"/>
<channel id="range" typeId="range"/>
<channel id="reardefroster" typeId="reardefroster"/>
<channel id="reartrunk" typeId="reartrunk"/>
<channel id="remotestart" typeId="remotestart"/>
<channel id="remotestartenabled" typeId="remotestartenabled"/>
<channel id="remotestartsupported" typeId="remotestartsupported"/>
<channel id="rightrearbackseatheater" typeId="rightrearbackseatheater"/>
<channel id="rightrearseatheater" typeId="rightrearseatheater"/>
<channel id="rightseatheater" typeId="rightseatheater"/>
<channel id="scheduledchargingpending" typeId="scheduledchargingpending"/>
<channel id="scheduledchargingstart" typeId="scheduledchargingstart"/>
<channel id="shiftstate" typeId="shiftstate"/>
<channel id="sidemirrorheaters" typeId="sidemirrorheaters"/>
<channel id="smartpreconditioning" typeId="smartpreconditioning"/>
<channel id="soc" typeId="soc"/>
<channel id="speed" typeId="speed"/>
<channel id="state" typeId="state"/>
<channel id="steeringwheelheater" typeId="steeringwheelheater"/>
<channel id="sunroof" typeId="sunroof"/>
<channel id="sunroofstate" typeId="sunroofstate"/>
<channel id="combinedtemp" typeId="combinedtemp"/>
<channel id="timetofullcharge" typeId="timetofullcharge"/>
<channel id="tripcharging" typeId="tripcharging"/>
<channel id="usablebatterylevel" typeId="usablebatterylevel"/>
<channel id="userchargeenablerequest" typeId="userchargeenablerequest"/>
<channel id="valetmode" typeId="valetmode"/>
<channel id="valetpin" typeId="valetpin"/>
<channel id="wakeup" typeId="wakeup"/>
<channel id="wiperbladeheater" typeId="wiperbladeheater"/>
</channels>
<config-description>
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number</label>
<description>VIN of the vehicle</description>
</parameter>
<parameter name="valetpin" type="integer" min="0" max="9999" required="false">
<context>password</context>
<label>Valet PIN</label>
<description>PIN to use when enabling Valet Mode</description>
</parameter>
<parameter name="allowWakeup" type="boolean" required="false">
<default>false</default>
<label>Allow Wake-Up</label>
<description>Allows waking up the vehicle. Caution: This can result in huge vampire drain!</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="tesla"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="modely">
<supported-bridge-type-refs>
<bridge-type-ref id="account"/>
</supported-bridge-type-refs>
<label>Tesla Model Y</label>
<description>A Tesla Model Y Vehicle</description>
<channels>
<channel id="allowwakeup" typeId="allowwakeup"/>
<channel id="autoconditioning" typeId="autoconditioning"/>
<channel id="autoparkstate" typeId="autoparkstate"/>
<channel id="autoparkstate2" typeId="autoparkstate"/>
<channel id="autoparkstyle" typeId="autoparkstyle"/>
<channel id="batterycurrent" typeId="batterycurrent"/>
<channel id="batterylevel" typeId="batterylevel"/>
<channel id="batteryrange" typeId="batteryrange"/>
<channel id="calendarenabled" typeId="calendarenabled"/>
<channel id="centerdisplay" typeId="centerdisplay"/>
<channel id="centerrearseatheater" typeId="centerrearseatheater"/>
<channel id="charge" typeId="charge"/>
<channel id="chargecable" typeId="chargecable"/>
<channel id="chargecurrent" typeId="chargecurrent"/>
<channel id="chargeenablerequest" typeId="chargeenablerequest"/>
<channel id="chargeenergyadded" typeId="chargeenergyadded"/>
<channel id="chargeidealdistanceadded" typeId="chargeidealdistanceadded"/>
<channel id="chargelimit" typeId="chargelimit"/>
<channel id="chargelimitmaximum" typeId="chargelimitmaximum"/>
<channel id="chargelimitminimum" typeId="chargelimitminimum"/>
<channel id="chargelimitsocstandard" typeId="chargelimitsocstandard"/>
<channel id="chargemaxcurrent" typeId="chargemaxcurrent"/>
<channel id="chargeport" typeId="chargeport"/>
<channel id="chargerate" typeId="chargerate"/>
<channel id="chargerateddistanceadded" typeId="chargerateddistanceadded"/>
<channel id="chargercurrent" typeId="chargercurrent"/>
<channel id="chargermaxcurrent" typeId="chargermaxcurrent"/>
<channel id="chargerphases" typeId="chargerphases"/>
<channel id="chargerpower" typeId="chargerpower"/>
<channel id="chargervoltage" typeId="chargervoltage"/>
<channel id="chargestartingrange" typeId="chargestartingrange"/>
<channel id="chargestartingsoc" typeId="chargestartingsoc"/>
<channel id="chargetomax" typeId="chargetomax"/>
<channel id="chargingstate" typeId="chargingstate"/>
<channel id="climate" typeId="climate"/>
<channel id="doorlock" typeId="doorlock"/>
<channel id="driverfrontdoor" typeId="driverfrontdoor"/>
<channel id="driverreardoor" typeId="driverreardoor"/>
<channel id="drivertemp" typeId="drivertemp"/>
<channel id="estimatedbatteryrange" typeId="estimatedbatteryrange"/>
<channel id="estimatedrange" typeId="estimatedrange"/>
<channel id="eventstamp" typeId="eventstamp"/>
<channel id="fan" typeId="fan"/>
<channel id="flashlights" typeId="flashlights"/>
<channel id="frontdefroster" typeId="frontdefroster"/>
<channel id="fronttrunk" typeId="fronttrunk"/>
<channel id="gpstimestamp" typeId="gpstimestamp"/>
<channel id="heading" typeId="heading"/>
<channel id="headingestimation" typeId="headingestimation"/>
<channel id="homelink" typeId="homelink"/>
<channel id="honkhorn" typeId="honkhorn"/>
<channel id="idealbatteryrange" typeId="idealbatteryrange"/>
<channel id="insidetemp" typeId="insidetemp"/>
<channel id="leftrearbackseatheater" typeId="leftrearbackseatheater"/>
<channel id="leftrearseatheater" typeId="leftrearseatheater"/>
<channel id="leftseatheater" typeId="leftseatheater"/>
<channel id="lefttempdirection" typeId="lefttempdirection"/>
<channel id="location" typeId="location"/>
<channel id="managedcharging" typeId="managedcharging"/>
<channel id="managedchargingcancelled" typeId="managedchargingcancelled"/>
<channel id="managedchargingstart" typeId="managedchargingstart"/>
<channel id="maxavailabletemp" typeId="maxavailabletemp"/>
<channel id="maxcharges" typeId="maxcharges"/>
<channel id="mobileenabled" typeId="mobileenabled"/>
<channel id="minavailabletemp" typeId="minavailabletemp"/>
<channel id="nativelocation" typeId="location"/>
<channel id="notenoughpower" typeId="notenoughpower"/>
<channel id="notificationsenabled" typeId="notificationsenabled"/>
<channel id="notificationssupported" typeId="notificationssupported"/>
<channel id="odometer" typeId="odometer"/>
<channel id="outsidetemp" typeId="outsidetemp"/>
<channel id="parsedcalendar" typeId="parsedcalendar"/>
<channel id="passengerfrontdoor" typeId="passengerfrontdoor"/>
<channel id="passengerreardoor" typeId="passengerreardoor"/>
<channel id="passengertemp" typeId="passengertemp"/>
<channel id="power" typeId="power"/>
<channel id="preconditioning" typeId="preconditioning"/>
<channel id="range" typeId="range"/>
<channel id="reardefroster" typeId="reardefroster"/>
<channel id="reartrunk" typeId="reartrunk"/>
<channel id="remotestart" typeId="remotestart"/>
<channel id="remotestartenabled" typeId="remotestartenabled"/>
<channel id="remotestartsupported" typeId="remotestartsupported"/>
<channel id="rightrearbackseatheater" typeId="rightrearbackseatheater"/>
<channel id="rightrearseatheater" typeId="rightrearseatheater"/>
<channel id="rightseatheater" typeId="rightseatheater"/>
<channel id="scheduledchargingpending" typeId="scheduledchargingpending"/>
<channel id="scheduledchargingstart" typeId="scheduledchargingstart"/>
<channel id="shiftstate" typeId="shiftstate"/>
<channel id="sidemirrorheaters" typeId="sidemirrorheaters"/>
<channel id="smartpreconditioning" typeId="smartpreconditioning"/>
<channel id="soc" typeId="soc"/>
<channel id="speed" typeId="speed"/>
<channel id="state" typeId="state"/>
<channel id="combinedtemp" typeId="combinedtemp"/>
<channel id="timetofullcharge" typeId="timetofullcharge"/>
<channel id="tripcharging" typeId="tripcharging"/>
<channel id="usablebatterylevel" typeId="usablebatterylevel"/>
<channel id="userchargeenablerequest" typeId="userchargeenablerequest"/>
<channel id="valetmode" typeId="valetmode"/>
<channel id="valetpin" typeId="valetpin"/>
<channel id="wakeup" typeId="wakeup"/>
</channels>
<config-description>
<parameter name="vin" type="text" required="true">
<label>Vehicle Identification Number</label>
<description>VIN of the vehicle</description>
</parameter>
<parameter name="valetpin" type="integer" min="0" max="9999" required="false">
<context>password</context>
<label>Valet PIN</label>
<description>PIN to use when enabling Valet Mode</description>
</parameter>
<parameter name="allowWakeup" type="boolean" required="false">
<default>false</default>
<label>Allow Wake-Up</label>
<description>Allows waking up the vehicle. Caution: This can result in huge vampire drain!</description>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>