[tesla] Remove (broken) options to obtain refresh token through credentials (#12537)
* Remove (broken) option to obtain refresh token through credentials * Remove outdated event streaming code * Update README Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
parent
8c6534300a
commit
9d2b04de33
|
@ -5,7 +5,8 @@ The integration happens through the Tesla Owners Remote API.
|
||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
All current Tesla models are supported by this binding. Access is established through a Tesla account as a bridge.
|
All current Tesla models are supported by this binding.
|
||||||
|
Access is established through a Tesla account as a bridge.
|
||||||
|
|
||||||
| Thing Type | Description |
|
| Thing Type | Description |
|
||||||
|------------|----------------------------------------------|
|
|------------|----------------------------------------------|
|
||||||
|
@ -19,42 +20,17 @@ All current Tesla models are supported by this binding. Access is established th
|
||||||
|
|
||||||
## Auto Discovery
|
## Auto Discovery
|
||||||
|
|
||||||
If the authentication with the Tesla Account is done through the openHAB console (see "Bridge Configuration" option 1 below), the account is automatically added to the Inbox.
|
The account cannot be automatically discovered, but has to be created manually.
|
||||||
|
|
||||||
|
Once an account is configured, it is automatically queried for associated vehicles and an Inbox entry is created for each of them.
|
||||||
|
|
||||||
|
Note: Vehicles that are asleep might not be discovered, so you might want to wake it up through the Tesla app first.
|
||||||
|
|
||||||
Furthermore, once an account is configured, it is automatically queried for associated vehicles and an Inbox entry is created for each of them.
|
|
||||||
|
|
||||||
## Bridge Configuration
|
## Bridge Configuration
|
||||||
|
|
||||||
The `account` bridge requires an OAuth2 refresh token as the only parameter `refreshToken`.
|
The `account` bridge requires an OAuth2 refresh token as the only parameter `refreshToken`.
|
||||||
|
|
||||||
There are three different ways of obtaining the token.
|
|
||||||
|
|
||||||
NOTE: Tesla has introduced some captcha mechanism, which might prevent options 1 and 2 from working as expected.
|
|
||||||
In case you are only receiving error messages, please make use of option 3!
|
|
||||||
|
|
||||||
1. Use the openHAB console
|
|
||||||
|
|
||||||
Run the following command on the console and provide your Tesla account credentials (the same that you use in the official Tesla app):
|
|
||||||
|
|
||||||
```
|
|
||||||
openhab> openhab:tesla login
|
|
||||||
Username (email): mail@example.com
|
|
||||||
Password: topsecret
|
|
||||||
Attempting login...Attempting login...
|
|
||||||
Refresh token: xxxxxxxxxx
|
|
||||||
```
|
|
||||||
When successfully doing the login through the console, openHAB will automatically create an Inbox entry that is preconfigured with the refresh token, which you can now simply approve.
|
|
||||||
|
|
||||||
Alternatively, you can use the refresh token to textually configure your `account` bridge or enter it in a manually created "Tesla Account" thing in the UI.
|
|
||||||
|
|
||||||
2. Provide your credentials in the UI
|
|
||||||
|
|
||||||
If you do not want to use the openHAB console, you can also manually create a "Tesla Account" thing in the UI by providing your username and password as parameters (to show them, use the "Show More" button) in the "Edit Thing" view and leaving the refresh token parameter field empty.
|
|
||||||
|
|
||||||
openHAB will use the provided credentials to retrieve and set the refresh token and automatically delete your password from the configuration afterwards for safety reasons.
|
|
||||||
|
|
||||||
3. Use external tools
|
|
||||||
|
|
||||||
There are a few 3rd party tools available that have specialized on getting hold of refresh tokens for the Tesla API.
|
There are a few 3rd party tools available that have specialized on getting hold of refresh tokens for the Tesla API.
|
||||||
Please note that we in general consider it dangerous to enter your credentials into some 3rd party app - you will have to trust the author not to send or store those credentials anywhere.
|
Please note that we in general consider it dangerous to enter your credentials into some 3rd party app - you will have to trust the author not to send or store those credentials anywhere.
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,4 @@ public class TeslaBindingConstants {
|
||||||
public static final String CONFIG_ALLOWWAKEUPFORCOMMANDS = "allowWakeupForCommands";
|
public static final String CONFIG_ALLOWWAKEUPFORCOMMANDS = "allowWakeupForCommands";
|
||||||
public static final String CONFIG_ENABLEEVENTS = "enableEvents";
|
public static final String CONFIG_ENABLEEVENTS = "enableEvents";
|
||||||
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
|
public static final String CONFIG_REFRESHTOKEN = "refreshToken";
|
||||||
public static final String CONFIG_USERNAME = "username";
|
|
||||||
public static final String CONFIG_PASSWORD = "password";
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.tesla.internal.command;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
|
|
||||||
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.handler.TeslaSSOHandler;
|
|
||||||
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.io.net.http.HttpClientFactory;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
public class TeslaCommandExtension extends AbstractConsoleCommandExtension {
|
|
||||||
|
|
||||||
private static final String CMD_LOGIN = "login";
|
|
||||||
|
|
||||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL)
|
|
||||||
private @Nullable ClientBuilder injectedClientBuilder;
|
|
||||||
|
|
||||||
private final TeslaAccountDiscoveryService teslaAccountDiscoveryService;
|
|
||||||
private final HttpClientFactory httpClientFactory;
|
|
||||||
|
|
||||||
@Activate
|
|
||||||
public TeslaCommandExtension(@Reference TeslaAccountDiscoveryService teslaAccountDiscoveryService,
|
|
||||||
@Reference HttpClientFactory httpClientFactory) {
|
|
||||||
super("tesla", "Interact with the Tesla integration.");
|
|
||||||
this.teslaAccountDiscoveryService = teslaAccountDiscoveryService;
|
|
||||||
this.httpClientFactory = httpClientFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
printUsage(console);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
TeslaSSOHandler ssoHandler = new TeslaSSOHandler(httpClientFactory.getCommonHttpClient());
|
|
||||||
|
|
||||||
String refreshToken = ssoHandler.authenticate(username, password);
|
|
||||||
if (refreshToken != null) {
|
|
||||||
console.println("Refresh token: " + refreshToken);
|
|
||||||
|
|
||||||
ThingUID thingUID = new ThingUID(TeslaBindingConstants.THING_TYPE_ACCOUNT, UIDUtils.encode(username));
|
|
||||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withLabel("Tesla Account")
|
|
||||||
.withProperty(TeslaBindingConstants.CONFIG_REFRESHTOKEN, refreshToken)
|
|
||||||
.withProperty(TeslaBindingConstants.CONFIG_USERNAME, username)
|
|
||||||
.withRepresentationProperty(TeslaBindingConstants.CONFIG_USERNAME).build();
|
|
||||||
teslaAccountDiscoveryService.thingDiscovered(result);
|
|
||||||
} else {
|
|
||||||
console.println("Failed to retrieve refresh token");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
|
||||||
*
|
|
||||||
* See the NOTICE file(s) distributed with this work for additional
|
|
||||||
* information.
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made available under the
|
|
||||||
* terms of the Eclipse Public License 2.0 which is available at
|
|
||||||
* http://www.eclipse.org/legal/epl-2.0
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.openhab.binding.tesla.internal.discovery;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
|
||||||
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 })
|
|
||||||
@NonNullByDefault
|
|
||||||
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<String, Object> configProperties) {
|
|
||||||
super.activate(configProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deactivate() {
|
|
||||||
super.deactivate();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void thingDiscovered(DiscoveryResult discoveryResult) {
|
|
||||||
super.thingDiscovered(discoveryResult);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,12 +14,9 @@ package org.openhab.binding.tesla.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
@ -30,20 +27,15 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import javax.ws.rs.client.Client;
|
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.Entity;
|
||||||
import javax.ws.rs.client.WebTarget;
|
import javax.ws.rs.client.WebTarget;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
|
|
||||||
import org.openhab.binding.tesla.internal.discovery.TeslaVehicleDiscoveryService;
|
import org.openhab.binding.tesla.internal.discovery.TeslaVehicleDiscoveryService;
|
||||||
import org.openhab.binding.tesla.internal.protocol.Vehicle;
|
import org.openhab.binding.tesla.internal.protocol.Vehicle;
|
||||||
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
|
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
|
||||||
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
|
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
|
||||||
import org.openhab.core.config.core.Configuration;
|
|
||||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
@ -269,33 +261,11 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasExpired) {
|
if (hasExpired) {
|
||||||
String username = (String) getConfig().get(CONFIG_USERNAME);
|
|
||||||
String password = (String) getConfig().get(CONFIG_PASSWORD);
|
|
||||||
String refreshToken = (String) getConfig().get(CONFIG_REFRESHTOKEN);
|
String refreshToken = (String) getConfig().get(CONFIG_REFRESHTOKEN);
|
||||||
|
|
||||||
if (refreshToken == null || refreshToken.isEmpty()) {
|
if (refreshToken == null || refreshToken.isEmpty()) {
|
||||||
if (username != null && !username.isEmpty() && password != null && !password.isEmpty()) {
|
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
try {
|
"No refresh token is provided.");
|
||||||
refreshToken = ssoHandler.authenticate(username, password);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("An exception occurred while obtaining refresh token with username/password: '{}'",
|
|
||||||
e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (refreshToken != null) {
|
|
||||||
// store refresh token from SSO endpoint in config, clear the password
|
|
||||||
Configuration cfg = editConfiguration();
|
|
||||||
cfg.put(TeslaBindingConstants.CONFIG_REFRESHTOKEN, refreshToken);
|
|
||||||
cfg.remove(TeslaBindingConstants.CONFIG_PASSWORD);
|
|
||||||
updateConfiguration(cfg);
|
|
||||||
} else {
|
|
||||||
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Failed to obtain refresh token with username/password.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
|
||||||
"Neither a refresh token nor credentials are provided.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logonToken = ssoHandler.getAccessToken(refreshToken);
|
this.logonToken = ssoHandler.getAccessToken(refreshToken);
|
||||||
|
@ -432,28 +402,6 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
protected class Request implements Runnable {
|
||||||
|
|
||||||
private static final int NO_OF_RETRIES = 3;
|
private static final int NO_OF_RETRIES = 3;
|
||||||
|
|
|
@ -14,11 +14,6 @@ package org.openhab.binding.tesla.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
@ -27,17 +22,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.util.FormContentProvider;
|
|
||||||
import org.eclipse.jetty.client.util.StringContentProvider;
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.util.Fields;
|
|
||||||
import org.eclipse.jetty.util.Fields.Field;
|
|
||||||
import org.jsoup.Jsoup;
|
|
||||||
import org.jsoup.nodes.Document;
|
|
||||||
import org.jsoup.nodes.Element;
|
|
||||||
import org.openhab.binding.tesla.internal.protocol.sso.AuthorizationCodeExchangeRequest;
|
|
||||||
import org.openhab.binding.tesla.internal.protocol.sso.AuthorizationCodeExchangeResponse;
|
|
||||||
import org.openhab.binding.tesla.internal.protocol.sso.RefreshTokenRequest;
|
import org.openhab.binding.tesla.internal.protocol.sso.RefreshTokenRequest;
|
||||||
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
|
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -93,178 +80,6 @@ public class TeslaSSOHandler {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Authenticates using username/password against Tesla SSO endpoints.
|
|
||||||
*
|
|
||||||
* @param username Username
|
|
||||||
* @param password Password
|
|
||||||
* @return Refresh token for use with {@link getAccessToken}
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public String authenticate(String username, String password) {
|
|
||||||
String codeVerifier = generateRandomString(86);
|
|
||||||
String codeChallenge = null;
|
|
||||||
String state = generateRandomString(10);
|
|
||||||
|
|
||||||
try {
|
|
||||||
codeChallenge = getCodeChallenge(codeVerifier);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
logger.error("An exception occurred while building login page request: '{}'", e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final org.eclipse.jetty.client.api.Request loginPageRequest = httpClient
|
|
||||||
.newRequest(URI_SSO + "/" + PATH_AUTHORIZE);
|
|
||||||
loginPageRequest.method(HttpMethod.GET);
|
|
||||||
loginPageRequest.followRedirects(false);
|
|
||||||
|
|
||||||
addQueryParameters(loginPageRequest, codeChallenge, state);
|
|
||||||
|
|
||||||
ContentResponse loginPageResponse = executeHttpRequest(loginPageRequest);
|
|
||||||
if (loginPageResponse == null
|
|
||||||
|| (loginPageResponse.getStatus() != 200 && loginPageResponse.getStatus() != 302)) {
|
|
||||||
logger.debug("Failed to obtain SSO login page, response status code: {}",
|
|
||||||
(loginPageResponse != null ? loginPageResponse.getStatus() : "no response"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Obtained SSO login page");
|
|
||||||
|
|
||||||
String authorizationCode = null;
|
|
||||||
|
|
||||||
if (loginPageResponse.getStatus() == 302) {
|
|
||||||
String redirectLocation = loginPageResponse.getHeaders().get(HttpHeader.LOCATION);
|
|
||||||
if (isValidRedirectLocation(redirectLocation)) {
|
|
||||||
authorizationCode = extractAuthorizationCodeFromUri(redirectLocation);
|
|
||||||
} else {
|
|
||||||
logger.debug("Unexpected redirect location received when fetching login page: {}", redirectLocation);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Fields postData = new Fields();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Document doc = Jsoup.parse(loginPageResponse.getContentAsString());
|
|
||||||
logger.trace("{}", doc.toString());
|
|
||||||
Element loginForm = doc.getElementsByTag("form").first();
|
|
||||||
|
|
||||||
Iterator<Element> elIt = loginForm.getElementsByTag("input").iterator();
|
|
||||||
while (elIt.hasNext()) {
|
|
||||||
Element input = elIt.next();
|
|
||||||
if (input.attr("type").equalsIgnoreCase("hidden")) {
|
|
||||||
postData.add(input.attr("name"), input.attr("value"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("Failed to parse login page: {}", e.getMessage());
|
|
||||||
logger.debug("login page response {}", loginPageResponse.getContentAsString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
postData.add("identity", username);
|
|
||||||
postData.add("credential", password);
|
|
||||||
|
|
||||||
final org.eclipse.jetty.client.api.Request formSubmitRequest = httpClient
|
|
||||||
.newRequest(URI_SSO + "/" + PATH_AUTHORIZE);
|
|
||||||
formSubmitRequest.method(HttpMethod.POST);
|
|
||||||
formSubmitRequest.content(new FormContentProvider(postData));
|
|
||||||
formSubmitRequest.followRedirects(false); // this should return a 302 ideally, but that location doesn't
|
|
||||||
// exist
|
|
||||||
addQueryParameters(formSubmitRequest, codeChallenge, state);
|
|
||||||
|
|
||||||
ContentResponse formSubmitResponse = executeHttpRequest(formSubmitRequest);
|
|
||||||
if (formSubmitResponse == null || formSubmitResponse.getStatus() != 302) {
|
|
||||||
logger.debug("Failed to obtain code from SSO login page when submitting form, response status code: {}",
|
|
||||||
(formSubmitResponse != null ? formSubmitResponse.getStatus() : "no response"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String redirectLocation = formSubmitResponse.getHeaders().get(HttpHeader.LOCATION);
|
|
||||||
if (!isValidRedirectLocation(redirectLocation)) {
|
|
||||||
logger.debug("Redirect location not set or doesn't match expected callback URI {}: {}", URI_CALLBACK,
|
|
||||||
redirectLocation);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("Obtained valid redirect location");
|
|
||||||
authorizationCode = extractAuthorizationCodeFromUri(redirectLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (authorizationCode == null) {
|
|
||||||
logger.debug("Did not receive an authorization code");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// exchange authorization code for SSO access + refresh token
|
|
||||||
AuthorizationCodeExchangeRequest request = new AuthorizationCodeExchangeRequest(authorizationCode,
|
|
||||||
codeVerifier);
|
|
||||||
String payload = gson.toJson(request);
|
|
||||||
|
|
||||||
final org.eclipse.jetty.client.api.Request tokenExchangeRequest = httpClient
|
|
||||||
.newRequest(URI_SSO + "/" + PATH_TOKEN);
|
|
||||||
tokenExchangeRequest.content(new StringContentProvider(payload));
|
|
||||||
tokenExchangeRequest.header(HttpHeader.CONTENT_TYPE, "application/json");
|
|
||||||
tokenExchangeRequest.method(HttpMethod.POST);
|
|
||||||
|
|
||||||
ContentResponse response = executeHttpRequest(tokenExchangeRequest);
|
|
||||||
if (response != null && response.getStatus() == 200) {
|
|
||||||
String responsePayload = response.getContentAsString();
|
|
||||||
AuthorizationCodeExchangeResponse ssoTokenResponse = gson.fromJson(responsePayload.trim(),
|
|
||||||
AuthorizationCodeExchangeResponse.class);
|
|
||||||
if (ssoTokenResponse != null && ssoTokenResponse.token_type != null
|
|
||||||
&& !ssoTokenResponse.access_token.isEmpty()) {
|
|
||||||
logger.debug("Obtained valid SSO refresh token");
|
|
||||||
return ssoTokenResponse.refresh_token;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("An error occurred while exchanging authorization code for SSO refresh token: {}",
|
|
||||||
(response != null ? response.getStatus() : "no response"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean isValidRedirectLocation(@Nullable String redirectLocation) {
|
|
||||||
return redirectLocation != null && redirectLocation.startsWith(URI_CALLBACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String extractAuthorizationCodeFromUri(String uri) {
|
|
||||||
Field code = httpClient.newRequest(uri).getParams().get("code");
|
|
||||||
return code != null ? code.getValue() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCodeChallenge(String codeVerifier) throws NoSuchAlgorithmException {
|
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
byte[] hash = digest.digest(codeVerifier.getBytes());
|
|
||||||
|
|
||||||
StringBuilder hashStr = new StringBuilder(hash.length * 2);
|
|
||||||
for (byte b : hash) {
|
|
||||||
hashStr.append(String.format("%02x", b));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Base64.getUrlEncoder().encodeToString(hashStr.toString().getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String generateRandomString(int length) {
|
|
||||||
Random random = new Random();
|
|
||||||
|
|
||||||
String generatedString = random.ints('a', 'z' + 1).limit(length)
|
|
||||||
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
|
|
||||||
|
|
||||||
return generatedString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addQueryParameters(org.eclipse.jetty.client.api.Request request, String codeChallenge, String state) {
|
|
||||||
request.param("client_id", CLIENT_ID);
|
|
||||||
request.param("code_challenge", codeChallenge);
|
|
||||||
request.param("code_challenge_method", "S256");
|
|
||||||
request.param("redirect_uri", URI_CALLBACK);
|
|
||||||
request.param("response_type", "code");
|
|
||||||
request.param("scope", SSO_SCOPES);
|
|
||||||
request.param("state", state);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private ContentResponse executeHttpRequest(org.eclipse.jetty.client.api.Request request) {
|
private ContentResponse executeHttpRequest(org.eclipse.jetty.client.api.Request request) {
|
||||||
request.timeout(10, TimeUnit.SECONDS);
|
request.timeout(10, TimeUnit.SECONDS);
|
||||||
|
|
|
@ -14,14 +14,9 @@ package org.openhab.binding.tesla.internal.handler;
|
||||||
|
|
||||||
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -29,7 +24,6 @@ import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.measure.quantity.Temperature;
|
import javax.measure.quantity.Temperature;
|
||||||
import javax.ws.rs.ProcessingException;
|
import javax.ws.rs.ProcessingException;
|
||||||
|
@ -41,10 +35,8 @@ import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
|
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
|
||||||
import org.openhab.binding.tesla.internal.TeslaBindingConstants.EventKeys;
|
|
||||||
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy;
|
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy;
|
||||||
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy.TeslaChannelSelector;
|
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy.TeslaChannelSelector;
|
||||||
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler.Authenticator;
|
|
||||||
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler.Request;
|
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler.Request;
|
||||||
import org.openhab.binding.tesla.internal.protocol.ChargeState;
|
import org.openhab.binding.tesla.internal.protocol.ChargeState;
|
||||||
import org.openhab.binding.tesla.internal.protocol.ClimateState;
|
import org.openhab.binding.tesla.internal.protocol.ClimateState;
|
||||||
|
@ -69,7 +61,6 @@ import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.openhab.core.types.State;
|
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -88,13 +79,8 @@ import com.google.gson.JsonParser;
|
||||||
*/
|
*/
|
||||||
public class TeslaVehicleHandler extends BaseThingHandler {
|
public class TeslaVehicleHandler extends BaseThingHandler {
|
||||||
|
|
||||||
private static final int EVENT_STREAM_PAUSE = 5000;
|
|
||||||
private static final int EVENT_TIMESTAMP_AGE_LIMIT = 3000;
|
|
||||||
private static final int EVENT_TIMESTAMP_MAX_DELTA = 10000;
|
|
||||||
private static final int FAST_STATUS_REFRESH_INTERVAL = 15000;
|
private static final int FAST_STATUS_REFRESH_INTERVAL = 15000;
|
||||||
private static final int SLOW_STATUS_REFRESH_INTERVAL = 60000;
|
private static final int SLOW_STATUS_REFRESH_INTERVAL = 60000;
|
||||||
private static final int EVENT_MAXIMUM_ERRORS_IN_INTERVAL = 10;
|
|
||||||
private static final int EVENT_ERROR_INTERVAL_SECONDS = 15;
|
|
||||||
private static final int API_SLEEP_INTERVAL_MINUTES = 20;
|
private static final int API_SLEEP_INTERVAL_MINUTES = 20;
|
||||||
private static final int MOVE_THRESHOLD_INTERVAL_MINUTES = 5;
|
private static final int MOVE_THRESHOLD_INTERVAL_MINUTES = 5;
|
||||||
|
|
||||||
|
@ -113,7 +99,6 @@ public class TeslaVehicleHandler extends BaseThingHandler {
|
||||||
|
|
||||||
protected boolean allowWakeUp;
|
protected boolean allowWakeUp;
|
||||||
protected boolean allowWakeUpForCommands;
|
protected boolean allowWakeUpForCommands;
|
||||||
protected boolean enableEvents = false;
|
|
||||||
protected long lastTimeStamp;
|
protected long lastTimeStamp;
|
||||||
protected long apiIntervalTimestamp;
|
protected long apiIntervalTimestamp;
|
||||||
protected int apiIntervalErrors;
|
protected int apiIntervalErrors;
|
||||||
|
@ -184,13 +169,6 @@ public class TeslaVehicleHandler extends BaseThingHandler {
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enableEvents) {
|
|
||||||
if (eventThread == null) {
|
|
||||||
eventThread = new Thread(eventRunnable, "openHAB-Tesla-Events-" + getThing().getUID());
|
|
||||||
eventThread.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1015,173 +993,4 @@ public class TeslaVehicleHandler extends BaseThingHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
protected Runnable eventRunnable = new Runnable() {
|
|
||||||
Response eventResponse;
|
|
||||||
BufferedReader eventBufferedReader;
|
|
||||||
InputStreamReader eventInputStreamReader;
|
|
||||||
boolean isEstablished = false;
|
|
||||||
|
|
||||||
protected boolean establishEventStream() {
|
|
||||||
try {
|
|
||||||
if (!isEstablished) {
|
|
||||||
eventBufferedReader = null;
|
|
||||||
|
|
||||||
eventClient = clientBuilder.build()
|
|
||||||
.register(new Authenticator((String) getConfig().get(CONFIG_USERNAME), vehicle.tokens[0]));
|
|
||||||
eventTarget = eventClient.target(URI_EVENT).path(vehicle.vehicle_id + "/").queryParam("values",
|
|
||||||
Arrays.asList(EventKeys.values()).stream().skip(1).map(Enum::toString)
|
|
||||||
.collect(Collectors.joining(",")));
|
|
||||||
eventResponse = eventTarget.request(MediaType.TEXT_PLAIN_TYPE).get();
|
|
||||||
|
|
||||||
logger.debug("Event Stream: Establishing the event stream: Response: {}:{}",
|
|
||||||
eventResponse.getStatus(), eventResponse.getStatusInfo());
|
|
||||||
|
|
||||||
if (eventResponse.getStatus() == 200) {
|
|
||||||
InputStream dummy = (InputStream) eventResponse.getEntity();
|
|
||||||
eventInputStreamReader = new InputStreamReader(dummy);
|
|
||||||
eventBufferedReader = new BufferedReader(eventInputStreamReader);
|
|
||||||
isEstablished = true;
|
|
||||||
} else if (eventResponse.getStatus() == 401) {
|
|
||||||
isEstablished = false;
|
|
||||||
} else {
|
|
||||||
isEstablished = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEstablished) {
|
|
||||||
eventIntervalErrors++;
|
|
||||||
if (eventIntervalErrors >= EVENT_MAXIMUM_ERRORS_IN_INTERVAL) {
|
|
||||||
logger.warn(
|
|
||||||
"Reached the maximum number of errors ({}) for the current interval ({} seconds)",
|
|
||||||
EVENT_MAXIMUM_ERRORS_IN_INTERVAL, EVENT_ERROR_INTERVAL_SECONDS);
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
|
||||||
eventClient.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((System.currentTimeMillis() - eventIntervalTimestamp) > 1000
|
|
||||||
* EVENT_ERROR_INTERVAL_SECONDS) {
|
|
||||||
logger.trace("Resetting the error counter. ({} errors in the last interval)",
|
|
||||||
eventIntervalErrors);
|
|
||||||
eventIntervalTimestamp = System.currentTimeMillis();
|
|
||||||
eventIntervalErrors = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(
|
|
||||||
"Event stream: An exception occurred while establishing the event stream for the vehicle: '{}'",
|
|
||||||
e.getMessage());
|
|
||||||
isEstablished = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isEstablished;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
|
||||||
if (isAwake()) {
|
|
||||||
if (establishEventStream()) {
|
|
||||||
String line = eventBufferedReader.readLine();
|
|
||||||
|
|
||||||
while (line != null) {
|
|
||||||
logger.debug("Event stream: Received an event: '{}'", line);
|
|
||||||
String vals[] = line.split(",");
|
|
||||||
long currentTimeStamp = Long.valueOf(vals[0]);
|
|
||||||
long systemTimeStamp = System.currentTimeMillis();
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
||||||
logger.debug("STS {} CTS {} Delta {}",
|
|
||||||
dateFormatter.format(new Date(systemTimeStamp)),
|
|
||||||
dateFormatter.format(new Date(currentTimeStamp)),
|
|
||||||
systemTimeStamp - currentTimeStamp);
|
|
||||||
}
|
|
||||||
if (systemTimeStamp - currentTimeStamp < EVENT_TIMESTAMP_AGE_LIMIT) {
|
|
||||||
if (currentTimeStamp > lastTimeStamp) {
|
|
||||||
lastTimeStamp = Long.valueOf(vals[0]);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
||||||
logger.debug("Event Stream: Event stamp is {}",
|
|
||||||
dateFormatter.format(new Date(lastTimeStamp)));
|
|
||||||
}
|
|
||||||
for (int i = 0; i < EventKeys.values().length; i++) {
|
|
||||||
TeslaChannelSelector selector = TeslaChannelSelector
|
|
||||||
.getValueSelectorFromRESTID((EventKeys.values()[i]).toString());
|
|
||||||
if (!selector.isProperty()) {
|
|
||||||
State newState = teslaChannelSelectorProxy.getState(vals[i],
|
|
||||||
selector, editProperties());
|
|
||||||
if (newState != null && !"".equals(vals[i])) {
|
|
||||||
updateState(selector.getChannelID(), newState);
|
|
||||||
} else {
|
|
||||||
updateState(selector.getChannelID(), UnDefType.UNDEF);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Map<String, String> properties = editProperties();
|
|
||||||
properties.put(selector.getChannelID(),
|
|
||||||
(selector.getState(vals[i])).toString());
|
|
||||||
updateProperties(properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
||||||
logger.debug(
|
|
||||||
"Event stream: Discarding an event with an out of sync timestamp {} (last is {})",
|
|
||||||
dateFormatter.format(new Date(currentTimeStamp)),
|
|
||||||
dateFormatter.format(new Date(lastTimeStamp)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
SimpleDateFormat dateFormatter = new SimpleDateFormat(
|
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS");
|
|
||||||
logger.debug(
|
|
||||||
"Event Stream: Discarding an event that differs {} ms from the system time: {} (system is {})",
|
|
||||||
systemTimeStamp - currentTimeStamp,
|
|
||||||
dateFormatter.format(currentTimeStamp),
|
|
||||||
dateFormatter.format(systemTimeStamp));
|
|
||||||
}
|
|
||||||
if (systemTimeStamp - currentTimeStamp > EVENT_TIMESTAMP_MAX_DELTA) {
|
|
||||||
logger.trace("Event stream: The event stream will be reset");
|
|
||||||
isEstablished = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
line = eventBufferedReader.readLine();
|
|
||||||
}
|
|
||||||
logger.trace("Event stream: The end of stream was reached");
|
|
||||||
isEstablished = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("Event stream: The vehicle is not awake");
|
|
||||||
if (vehicle != null) {
|
|
||||||
if (allowWakeUp) {
|
|
||||||
// wake up the vehicle until streaming token <> 0
|
|
||||||
logger.debug("Event stream: Waking up the vehicle");
|
|
||||||
wakeUp();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vehicle = queryVehicle();
|
|
||||||
}
|
|
||||||
Thread.sleep(EVENT_STREAM_PAUSE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException | NumberFormatException e) {
|
|
||||||
logger.debug("Event stream: An exception occurred while reading events: '{}'", e.getMessage());
|
|
||||||
isEstablished = false;
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
isEstablished = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Thread.interrupted()) {
|
|
||||||
logger.debug("Event stream: the event stream was interrupted");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ package org.openhab.binding.tesla.internal.protocol.sso;
|
||||||
* The {@link TokenResponse} is a datastructure to capture
|
* The {@link TokenResponse} is a datastructure to capture
|
||||||
* authentication response from Tesla Remote Service
|
* authentication response from Tesla Remote Service
|
||||||
*
|
*
|
||||||
* @author Nicolai Grødum
|
* @author Nicolai Grødum - Initial contribution
|
||||||
*/
|
*/
|
||||||
public class TokenResponse {
|
public class TokenResponse {
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,8 @@ thing-type.tesla.modely.description = A Tesla Model Y Vehicle
|
||||||
|
|
||||||
# thing types config
|
# thing types config
|
||||||
|
|
||||||
thing-type.config.tesla.account.password.label = Password
|
|
||||||
thing-type.config.tesla.account.password.description = Password for the Tesla Remote Service
|
|
||||||
thing-type.config.tesla.account.refreshToken.label = Refresh Token
|
thing-type.config.tesla.account.refreshToken.label = Refresh Token
|
||||||
thing-type.config.tesla.account.refreshToken.description = Refresh token for account authentication. Use "openhab:tesla" to retrieve it.
|
thing-type.config.tesla.account.refreshToken.description = Refresh token for account authentication. Please see the documentation on how to get hold of it.
|
||||||
thing-type.config.tesla.account.username.label = Username
|
|
||||||
thing-type.config.tesla.account.username.description = Username for the Tesla Remote Service, e.g email address.
|
|
||||||
thing-type.config.tesla.model3.allowWakeup.label = Allow Wake-Up
|
thing-type.config.tesla.model3.allowWakeup.label = Allow Wake-Up
|
||||||
thing-type.config.tesla.model3.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
|
thing-type.config.tesla.model3.allowWakeup.description = Allows waking up the vehicle. Caution: This can result in huge vampire drain!
|
||||||
thing-type.config.tesla.model3.allowWakeupForCommands.label = Allow Wake-Up For Commands
|
thing-type.config.tesla.model3.allowWakeupForCommands.label = Allow Wake-Up For Commands
|
||||||
|
|
|
@ -11,18 +11,7 @@
|
||||||
<config-description>
|
<config-description>
|
||||||
<parameter name="refreshToken" type="text" required="false">
|
<parameter name="refreshToken" type="text" required="false">
|
||||||
<label>Refresh Token</label>
|
<label>Refresh Token</label>
|
||||||
<description>Refresh token for account authentication. Use "openhab:tesla" to retrieve it.</description>
|
<description>Refresh token for account authentication. Please see the documentation on how to get hold of 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>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
|
Loading…
Reference in New Issue