added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user