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,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>