[tesla] Add null annotations (#17582)

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
This commit is contained in:
lsiepel 2024-12-08 14:35:41 +01:00 committed by GitHub
parent 931f39ff17
commit f39415b631
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 978 additions and 662 deletions

View File

@ -19,6 +19,8 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
@ -33,6 +35,7 @@ import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
/**
* The {@link TeslaChannelSelectorProxy} class is a helper class to instantiate
@ -40,6 +43,7 @@ import org.openhab.core.types.Type;
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public class TeslaChannelSelectorProxy {
public enum TeslaChannelSelector {
@ -939,11 +943,11 @@ public class TeslaChannelSelectorProxy {
@Override
public State getState(String s, TeslaChannelSelectorProxy proxy, Map<String, String> properties) {
State someState = super.getState(s);
if (someState != null) {
if (someState != UnDefType.UNDEF) {
BigDecimal value = ((DecimalType) someState).toBigDecimal();
return new QuantityType<>(value, ImperialUnits.MILES_PER_HOUR);
} else {
return null;
return UnDefType.UNDEF;
}
}
},
@ -1062,12 +1066,12 @@ public class TeslaChannelSelectorProxy {
},
WHEEL_TYPE("wheel_type", "wheeltype", StringType.class, true);
private final String restID;
private final @Nullable String restID;
private final String channelID;
private Class<? extends Type> typeClass;
private final boolean isProperty;
private TeslaChannelSelector(String restID, String channelID, Class<? extends Type> typeClass,
private TeslaChannelSelector(@Nullable String restID, String channelID, Class<? extends Type> typeClass,
boolean isProperty) {
this.restID = restID;
this.channelID = channelID;
@ -1077,7 +1081,8 @@ public class TeslaChannelSelectorProxy {
@Override
public String toString() {
return restID;
String restID = this.restID;
return restID != null ? restID : "null";
}
public String getChannelID() {
@ -1107,7 +1112,7 @@ public class TeslaChannelSelectorProxy {
| InvocationTargetException e) {
}
return null;
return UnDefType.UNDEF;
}
public static TeslaChannelSelector getValueSelectorFromChannelID(String valueSelectorText)
@ -1124,7 +1129,8 @@ public class TeslaChannelSelectorProxy {
public static TeslaChannelSelector getValueSelectorFromRESTID(String valueSelectorText)
throws IllegalArgumentException {
for (TeslaChannelSelector c : TeslaChannelSelector.values()) {
if (c.restID != null && c.restID.equals(valueSelectorText)) {
String restID = c.restID;
if (restID != null && restID.equals(valueSelectorText)) {
return c;
}
}

View File

@ -12,13 +12,14 @@
*/
package org.openhab.binding.tesla.internal.discovery;
import org.eclipse.jdt.annotation.NonNull;
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.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.binding.tesla.internal.protocol.dto.Vehicle;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleConfig;
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
@ -36,8 +37,9 @@ import org.slf4j.LoggerFactory;
* @author Kai Kreuzer - Initial contribution
*
*/
@NonNullByDefault
@Component(scope = ServiceScope.PROTOTYPE, service = TeslaVehicleDiscoveryService.class)
public class TeslaVehicleDiscoveryService extends AbstractThingHandlerDiscoveryService<@NonNull TeslaAccountHandler>
public class TeslaVehicleDiscoveryService extends AbstractThingHandlerDiscoveryService<TeslaAccountHandler>
implements VehicleListener {
private final Logger logger = LoggerFactory.getLogger(TeslaVehicleDiscoveryService.class);
@ -63,13 +65,13 @@ public class TeslaVehicleDiscoveryService extends AbstractThingHandlerDiscoveryS
}
@Override
public void vehicleFound(Vehicle vehicle, VehicleConfig vehicleConfig) {
public void vehicleFound(Vehicle vehicle, @Nullable VehicleConfig vehicleConfig) {
ThingTypeUID type = vehicleConfig == null ? TeslaBindingConstants.THING_TYPE_VEHICLE
: vehicleConfig.identifyModel();
if (type != null) {
logger.debug("Found a {} vehicle", type.getId());
ThingUID thingUID = new ThingUID(type, thingHandler.getThing().getUID(), vehicle.vin);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withLabel(vehicle.display_name)
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withLabel(vehicle.displayName)
.withBridge(thingHandler.getThing().getUID()).withProperty(TeslaBindingConstants.VIN, vehicle.vin)
.build();
thingDiscovered(discoveryResult);

View File

@ -31,12 +31,14 @@ 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.TeslaVehicleDiscoveryService;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
import org.openhab.binding.tesla.internal.protocol.VehicleData;
import org.openhab.binding.tesla.internal.protocol.sso.TokenResponse;
import org.openhab.binding.tesla.internal.protocol.dto.Vehicle;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleConfig;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleData;
import org.openhab.binding.tesla.internal.protocol.dto.sso.TokenResponse;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
@ -63,6 +65,7 @@ import com.google.gson.JsonParser;
* @author Nicolai Grødum - Adding token based auth
* @author Kai Kreuzer - refactored to use separate vehicle handlers
*/
@NonNullByDefault
public class TeslaAccountHandler extends BaseBridgeHandler {
public static final int API_MAXIMUM_ERRORS_IN_INTERVAL = 3;
@ -86,6 +89,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
private final ThingTypeMigrationService thingTypeMigrationService;
// Threading and Job related variables
@Nullable
protected ScheduledFuture<?> connectJob;
protected long lastTimeStamp;
@ -93,10 +97,12 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
protected int apiIntervalErrors;
protected long eventIntervalTimestamp;
protected int eventIntervalErrors;
protected ReentrantLock lock;
protected ReentrantLock lock = new ReentrantLock();
private final Gson gson = new Gson();
@Nullable
private TokenResponse logonToken;
private final Set<VehicleListener> vehicleListeners = new HashSet<>();
@ -122,31 +128,17 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
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();
}
connectJob = scheduler.scheduleWithFixedDelay(connectRunnable, 0, CONNECT_RETRY_INTERVAL,
TimeUnit.MILLISECONDS);
}
@Override
public void dispose() {
logger.debug("Disposing the Tesla account handler for {}", getThing().getUID());
lock.lock();
try {
if (connectJob != null && !connectJob.isCancelled()) {
connectJob.cancel(true);
connectJob = null;
}
} finally {
lock.unlock();
ScheduledFuture<?> connectJob = this.connectJob;
if (connectJob != null && !connectJob.isCancelled()) {
connectJob.cancel(true);
this.connectJob = null;
}
}
@ -167,19 +159,25 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
// we do not have any channels -> nothing to do here
}
public String getAuthHeader() {
if (logonToken != null) {
return "Bearer " + logonToken.access_token;
public @Nullable String getAuthHeader() {
String accessToken = getAccessToken();
if (accessToken != null) {
return "Bearer " + accessToken;
} else {
return null;
}
}
public String getAccessToken() {
return logonToken.access_token;
public @Nullable String getAccessToken() {
TokenResponse logonToken = this.logonToken;
if (logonToken != null) {
return logonToken.accessToken;
} else {
return null;
}
}
protected boolean checkResponse(Response response, boolean immediatelyFail) {
protected boolean checkResponse(@Nullable Response response, boolean immediatelyFail) {
if (response != null && response.getStatus() == 200) {
return true;
} else if (response != null && response.getStatus() == 401) {
@ -221,17 +219,23 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
if (!checkResponse(response, true)) {
logger.debug("An error occurred while querying the vehicle");
return null;
return new Vehicle[0];
}
JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
if (vehicleArray == null) {
logger.debug("Response resulted in unexpected null array");
return new Vehicle[0];
}
for (Vehicle vehicle : vehicleArray) {
String responseString = invokeAndParse(vehicle.id, null, null, dataRequestTarget, 0);
VehicleConfig vehicleConfig = null;
if (responseString != null && !responseString.isBlank()) {
vehicleConfig = gson.fromJson(responseString, VehicleData.class).vehicle_config;
VehicleData vehicleData = gson.fromJson(responseString, VehicleData.class);
if (vehicleData != null) {
vehicleConfig = vehicleData.vehicleConfig;
}
}
for (VehicleListener listener : vehicleListeners) {
listener.vehicleFound(vehicle, vehicleConfig);
@ -251,7 +255,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
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,
logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicleId,
vehicle.tokens);
}
}
@ -274,8 +278,8 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
logger.debug("Current authentication time {}", DATE_FORMATTER.format(Instant.now()));
if (token != null) {
Instant tokenCreationInstant = Instant.ofEpochMilli(token.created_at * 1000);
Instant tokenExpiresInstant = Instant.ofEpochMilli((token.created_at + token.expires_in) * 1000);
Instant tokenCreationInstant = Instant.ofEpochMilli(token.createdAt * 1000);
Instant tokenExpiresInstant = Instant.ofEpochMilli((token.createdAt + token.expiresIn) * 1000);
logger.debug("Found a request token from {}", DATE_FORMATTER.format(tokenCreationInstant));
logger.debug("Access token expiration time {}", DATE_FORMATTER.format(tokenExpiresInstant));
@ -306,8 +310,8 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
return new ThingStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
}
protected String invokeAndParse(String vehicleId, String command, String payLoad, WebTarget target,
int noOfretries) {
protected @Nullable String invokeAndParse(@Nullable String vehicleId, @Nullable String command,
@Nullable String payLoad, WebTarget target, int noOfretries) {
logger.debug("Invoking: {}", command);
if (vehicleId != null) {
@ -316,26 +320,29 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
if (payLoad != null) {
if (command != null) {
response = target.resolveTemplate("cmd", command).resolveTemplate("vid", vehicleId).request()
.header("Authorization", "Bearer " + logonToken.access_token)
.header("Authorization", getAuthHeader())
.post(Entity.entity(payLoad, MediaType.APPLICATION_JSON_TYPE));
} else {
response = target.resolveTemplate("vid", vehicleId).request()
.header("Authorization", "Bearer " + logonToken.access_token)
.header("Authorization", getAuthHeader())
.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();
.request(MediaType.APPLICATION_JSON_TYPE).header("Authorization", getAuthHeader()).get();
} else {
response = target.resolveTemplate("vid", vehicleId).request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer " + logonToken.access_token).get();
.header("Authorization", getAuthHeader()).get();
}
if (!checkResponse(response, false)) {
if (response == null) {
logger.debug(
"An error occurred while communicating with the vehicle during request, the response was null");
return null;
}
logger.debug("An error occurred while communicating with the vehicle during request {}: {}: {}",
command, (response != null) ? response.getStatus() : "",
(response != null) ? response.getStatusInfo().getReasonPhrase() : "No Response");
command, response.getStatus(), response.getStatusInfo().getReasonPhrase());
if (response.getStatus() == 408 && noOfretries > 0) {
try {
// we give the vehicle a moment to wake up and try the request again
@ -377,7 +384,7 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
if (authenticationResult.getStatus() == ThingStatus.ONLINE) {
// get a list of vehicles
Response response = productsTarget.request(MediaType.APPLICATION_JSON_TYPE)
.header("Authorization", "Bearer " + logonToken.access_token).get();
.header("Authorization", getAuthHeader()).get();
if (response != null && response.getStatus() == 200 && response.hasEntity()) {
updateStatus(ThingStatus.ONLINE);
@ -436,11 +443,12 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
private TeslaVehicleHandler handler;
private String request;
@Nullable
private String payLoad;
private WebTarget target;
private boolean allowWakeUpForCommands;
public Request(TeslaVehicleHandler handler, String request, String payLoad, WebTarget target,
public Request(TeslaVehicleHandler handler, String request, @Nullable String payLoad, WebTarget target,
boolean allowWakeUpForCommands) {
this.handler = handler;
this.request = request;
@ -467,8 +475,8 @@ public class TeslaAccountHandler extends BaseBridgeHandler {
}
}
public Request newRequest(TeslaVehicleHandler teslaVehicleHandler, String command, String payLoad, WebTarget target,
boolean allowWakeUpForCommands) {
public Request newRequest(TeslaVehicleHandler teslaVehicleHandler, String command, @Nullable String payLoad,
WebTarget target, boolean allowWakeUpForCommands) {
return new Request(teslaVehicleHandler, command, payLoad, target, allowWakeUpForCommands);
}

View File

@ -29,7 +29,7 @@ import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.api.WebSocketPingPongListener;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.tesla.internal.protocol.Event;
import org.openhab.binding.tesla.internal.protocol.dto.Event;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.util.ThingWebClientUtil;
@ -54,25 +54,20 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
private final Logger logger = LoggerFactory.getLogger(TeslaEventEndpoint.class);
private String endpointId;
protected WebSocketFactory webSocketFactory;
private WebSocketClient client;
private ConnectionState connectionState = ConnectionState.CLOSED;
private @Nullable Session session;
private EventHandler eventHandler;
private @Nullable EventHandler eventHandler;
private final Gson gson = new Gson();
public TeslaEventEndpoint(ThingUID uid, WebSocketFactory webSocketFactory) {
try {
this.endpointId = "TeslaEventEndpoint-" + uid.getAsString();
this.endpointId = "TeslaEventEndpoint-" + uid.getAsString();
String name = ThingWebClientUtil.buildWebClientConsumerName(uid, null);
client = webSocketFactory.createWebSocketClient(name);
this.client.setConnectTimeout(TIMEOUT_MILLISECONDS);
this.client.setMaxIdleTimeout(IDLE_TIMEOUT_MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
String name = ThingWebClientUtil.buildWebClientConsumerName(uid, null);
client = webSocketFactory.createWebSocketClient(name);
this.client.setConnectTimeout(TIMEOUT_MILLISECONDS);
this.client.setMaxIdleTimeout(IDLE_TIMEOUT_MILLISECONDS);
}
public void close() {
@ -117,19 +112,22 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
}
@Override
public void onWebSocketConnect(Session session) {
logger.debug("{} : Connected to {} with hash {}", endpointId, session.getRemoteAddress().getAddress(),
session.hashCode());
public void onWebSocketConnect(@Nullable Session session) {
logger.debug("{} : Connected to {} with hash {}", endpointId,
(session != null) ? session.getRemoteAddress().getAddress() : "Unknown",
(session != null) ? session.hashCode() : -1);
connectionState = ConnectionState.CONNECTED;
this.session = session;
}
public void closeConnection() {
Session session = this.session;
try {
connectionState = ConnectionState.CLOSING;
if (session != null && session.isOpen()) {
logger.debug("{} : Closing the session", endpointId);
session.close(StatusCode.NORMAL, "bye");
this.session = session;
}
} catch (Exception e) {
logger.error("{} : An exception occurred while closing the session : {}", endpointId, e.getMessage());
@ -138,14 +136,14 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
public void onWebSocketClose(int statusCode, @Nullable String reason) {
logger.debug("{} : Closed the session with status {} for reason {}", endpointId, statusCode, reason);
connectionState = ConnectionState.CLOSED;
this.session = null;
}
@Override
public void onWebSocketText(String message) {
public void onWebSocketText(@Nullable String message) {
// NoOp
}
@ -158,10 +156,13 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
try {
while ((str = in.readLine()) != null) {
logger.trace("{} : Received raw data '{}'", endpointId, str);
if (this.eventHandler != null) {
EventHandler eventHandler = this.eventHandler;
if (eventHandler != null) {
try {
Event event = gson.fromJson(str, Event.class);
this.eventHandler.handleEvent(event);
if (event != null) {
eventHandler.handleEvent(event);
}
} catch (RuntimeException e) {
logger.error("{} : An exception occurred while processing raw data : {}", endpointId,
e.getMessage());
@ -176,12 +177,14 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
@Override
public void onWebSocketError(Throwable cause) {
logger.error("{} : An error occurred in the session : {}", endpointId, cause.getMessage());
Session session = this.session;
if (session != null && session.isOpen()) {
session.close(StatusCode.ABNORMAL, "Session Error");
}
}
public void sendMessage(String message) throws IOException {
Session session = this.session;
try {
if (session != null) {
logger.debug("{} : Sending raw data '{}'", endpointId, message);
@ -198,6 +201,7 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
}
public void ping() {
Session session = this.session;
try {
if (session != null) {
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(System.nanoTime()).flip();
@ -209,8 +213,9 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
}
@Override
public void onWebSocketPing(ByteBuffer payload) {
public void onWebSocketPing(@Nullable ByteBuffer payload) {
ByteBuffer buffer = ByteBuffer.allocate(8).putLong(System.nanoTime()).flip();
Session session = this.session;
try {
if (session != null) {
session.getRemote().sendPing(buffer);
@ -221,7 +226,10 @@ public class TeslaEventEndpoint implements WebSocketListener, WebSocketPingPongL
}
@Override
public void onWebSocketPong(ByteBuffer payload) {
public void onWebSocketPong(@Nullable ByteBuffer payload) {
if (payload == null) {
return;
}
long start = payload.getLong();
long roundTrip = System.nanoTime() - start;

View File

@ -28,8 +28,8 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
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.dto.sso.RefreshTokenRequest;
import org.openhab.binding.tesla.internal.protocol.dto.sso.TokenResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -72,10 +72,10 @@ public class TeslaSSOHandler {
String refreshTokenResponse = refreshResponse.getContentAsString();
TokenResponse tokenResponse = gson.fromJson(refreshTokenResponse.trim(), TokenResponse.class);
if (tokenResponse != null && tokenResponse.access_token != null && !tokenResponse.access_token.isEmpty()) {
tokenResponse.created_at = Instant.now().getEpochSecond();
logger.debug("Access token expires in {} seconds at {}", tokenResponse.expires_in, DATE_FORMATTER
.format(Instant.ofEpochMilli((tokenResponse.created_at + tokenResponse.expires_in) * 1000)));
if (tokenResponse != null && tokenResponse.accessToken != null && !tokenResponse.accessToken.isEmpty()) {
tokenResponse.createdAt = Instant.now().getEpochSecond();
logger.debug("Access token expires in {} seconds at {}", tokenResponse.expiresIn, DATE_FORMATTER
.format(Instant.ofEpochMilli((tokenResponse.createdAt + tokenResponse.expiresIn) * 1000)));
return tokenResponse;
} else {
logger.debug("An error occurred while exchanging SSO auth token for API access token.");

View File

@ -25,6 +25,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
@ -37,21 +38,22 @@ 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.TeslaBindingConstants.EventKeys;
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy;
import org.openhab.binding.tesla.internal.TeslaChannelSelectorProxy.TeslaChannelSelector;
import org.openhab.binding.tesla.internal.handler.TeslaAccountHandler.Request;
import org.openhab.binding.tesla.internal.protocol.ChargeState;
import org.openhab.binding.tesla.internal.protocol.ClimateState;
import org.openhab.binding.tesla.internal.protocol.DriveState;
import org.openhab.binding.tesla.internal.protocol.Event;
import org.openhab.binding.tesla.internal.protocol.GUIState;
import org.openhab.binding.tesla.internal.protocol.SoftwareUpdate;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleData;
import org.openhab.binding.tesla.internal.protocol.VehicleState;
import org.openhab.binding.tesla.internal.protocol.dto.ChargeState;
import org.openhab.binding.tesla.internal.protocol.dto.ClimateState;
import org.openhab.binding.tesla.internal.protocol.dto.DriveState;
import org.openhab.binding.tesla.internal.protocol.dto.Event;
import org.openhab.binding.tesla.internal.protocol.dto.GUIState;
import org.openhab.binding.tesla.internal.protocol.dto.SoftwareUpdate;
import org.openhab.binding.tesla.internal.protocol.dto.Vehicle;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleData;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleState;
import org.openhab.binding.tesla.internal.throttler.QueueChannelThrottler;
import org.openhab.binding.tesla.internal.throttler.Rate;
import org.openhab.core.io.net.http.WebSocketFactory;
@ -64,6 +66,7 @@ import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
@ -88,9 +91,9 @@ import com.google.gson.JsonParser;
* @author Karel Goderis - Initial contribution
* @author Kai Kreuzer - Refactored to use separate account handler and improved configuration options
*/
@NonNullByDefault
public class TeslaVehicleHandler extends BaseThingHandler {
private static final int FAST_STATUS_REFRESH_INTERVAL = 15000;
private static final int SLOW_STATUS_REFRESH_INTERVAL = 60000;
private static final int API_SLEEP_INTERVAL_MINUTES = 20;
private static final int MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT = 5;
@ -105,13 +108,21 @@ public class TeslaVehicleHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(TeslaVehicleHandler.class);
// Vehicle state variables
@Nullable
protected Vehicle vehicle;
@Nullable
protected String vehicleJSON;
@Nullable
protected DriveState driveState;
@Nullable
protected GUIState guiState;
@Nullable
protected VehicleState vehicleState;
@Nullable
protected ChargeState chargeState;
@Nullable
protected ClimateState climateState;
@Nullable
protected SoftwareUpdate softwareUpdate;
protected boolean allowWakeUp;
@ -127,7 +138,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
protected long eventIntervalTimestamp;
protected int eventIntervalErrors;
protected int inactivity = MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT;
protected ReentrantLock lock;
protected ReentrantLock lock = new ReentrantLock();
protected double lastLongitude;
protected double lastLatitude;
@ -140,13 +151,13 @@ public class TeslaVehicleHandler extends BaseThingHandler {
protected String lastState = "";
protected boolean isInactive = false;
protected TeslaAccountHandler account;
protected @NonNullByDefault({}) TeslaAccountHandler account;
protected QueueChannelThrottler stateThrottler;
protected TeslaChannelSelectorProxy teslaChannelSelectorProxy = new TeslaChannelSelectorProxy();
protected Thread eventThread;
protected ScheduledFuture<?> stateJob;
protected @Nullable QueueChannelThrottler stateThrottler;
protected @Nullable Thread eventThread;
protected @Nullable ScheduledFuture<?> stateJob;
protected WebSocketFactory webSocketFactory;
protected TeslaChannelSelectorProxy teslaChannelSelectorProxy = new TeslaChannelSelectorProxy();
private final Gson gson = new Gson();
@ -155,68 +166,62 @@ public class TeslaVehicleHandler extends BaseThingHandler {
this.webSocketFactory = webSocketFactory;
}
@SuppressWarnings("null")
@Override
public void initialize() {
logger.trace("Initializing the Tesla handler for {}", getThing().getUID());
updateStatus(ThingStatus.UNKNOWN);
allowWakeUp = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUP);
allowWakeUpForCommands = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ALLOWWAKEUPFORCOMMANDS);
enableEvents = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_ENABLEEVENTS);
Number inactivityParam = (Number) getConfig().get(TeslaBindingConstants.CONFIG_INACTIVITY);
inactivity = inactivityParam == null ? MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT : inactivityParam.intValue();
Boolean useDriveStateParam = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDRIVESTATE);
useDriveState = useDriveStateParam == null ? false : useDriveStateParam;
Boolean useAdvancedStatesParam = (boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDADVANCEDSTATES);
useAdvancedStates = useAdvancedStatesParam == null ? false : useAdvancedStatesParam;
account = (TeslaAccountHandler) getBridge().getHandler();
inactivity = Objects.requireNonNullElse((Number) getConfig().get(TeslaBindingConstants.CONFIG_INACTIVITY),
MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT).intValue();
useDriveState = Objects
.requireNonNullElse((boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDRIVESTATE), false);
useAdvancedStates = Objects
.requireNonNullElse((boolean) getConfig().get(TeslaBindingConstants.CONFIG_USEDADVANCEDSTATES), false);
Bridge bridge = getBridge();
if (bridge == null || !(bridge.getHandler() instanceof TeslaAccountHandler teslaAccountHandler)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
return;
}
account = teslaAccountHandler;
lock = new ReentrantLock();
scheduler.execute(this::queryVehicleAndUpdate);
lock.lock();
try {
Map<Object, Rate> channels = new HashMap<>();
channels.put(DATA_THROTTLE, new Rate(1, 1, TimeUnit.SECONDS));
channels.put(COMMAND_THROTTLE, new Rate(20, 1, TimeUnit.MINUTES));
Map<Object, Rate> channels = new HashMap<>();
channels.put(DATA_THROTTLE, new Rate(1, 1, TimeUnit.SECONDS));
channels.put(COMMAND_THROTTLE, new Rate(20, 1, TimeUnit.MINUTES));
Rate firstRate = new Rate(20, 1, TimeUnit.MINUTES);
Rate secondRate = new Rate(200, 10, TimeUnit.MINUTES);
stateThrottler = new QueueChannelThrottler(firstRate, scheduler, channels);
stateThrottler.addRate(secondRate);
Rate firstRate = new Rate(20, 1, TimeUnit.MINUTES);
Rate secondRate = new Rate(200, 10, TimeUnit.MINUTES);
QueueChannelThrottler stateThrottler = new QueueChannelThrottler(firstRate, scheduler, channels);
stateThrottler.addRate(secondRate);
this.stateThrottler = stateThrottler;
stateJob = scheduler.scheduleWithFixedDelay(stateRunnable, 0, SLOW_STATUS_REFRESH_INTERVAL,
TimeUnit.MILLISECONDS);
if (stateJob == null || stateJob.isCancelled()) {
stateJob = scheduler.scheduleWithFixedDelay(stateRunnable, 0, SLOW_STATUS_REFRESH_INTERVAL,
TimeUnit.MILLISECONDS);
}
if (enableEvents) {
if (eventThread == null) {
eventThread = new Thread(eventRunnable, "OH-binding-" + getThing().getUID() + "-events");
eventThread.start();
}
}
} finally {
lock.unlock();
if (enableEvents) {
Thread eventThread = new Thread(eventRunnable, "OH-binding-" + getThing().getUID() + "-events");
eventThread.start();
this.eventThread = eventThread;
}
}
@Override
public void dispose() {
logger.trace("Disposing the Tesla handler for {}", getThing().getUID());
lock.lock();
try {
if (stateJob != null && !stateJob.isCancelled()) {
stateJob.cancel(true);
stateJob = null;
}
if (eventThread != null && !eventThread.isInterrupted()) {
eventThread.interrupt();
eventThread = null;
}
} finally {
lock.unlock();
ScheduledFuture<?> stateJob = this.stateJob;
if (stateJob != null && !stateJob.isCancelled()) {
stateJob.cancel(true);
this.stateJob = null;
}
Thread eventThread = this.eventThread;
if (eventThread != null && !eventThread.isInterrupted()) {
eventThread.interrupt();
this.eventThread = null;
}
}
@ -225,7 +230,8 @@ public class TeslaVehicleHandler extends BaseThingHandler {
*
* @return the vehicle id
*/
public String getVehicleId() {
public @Nullable String getVehicleId() {
Vehicle vehicle = this.vehicle;
if (vehicle != null) {
return vehicle.id;
} else {
@ -248,9 +254,9 @@ public class TeslaVehicleHandler extends BaseThingHandler {
setActive();
// Request the state of all known variables. This is sub-optimal, but the requests get scheduled and
// throttled so we are safe not to break the Tesla SLA
// throbridgettled so we are safe not to break the Tesla SLA
requestAllData();
} else if (selector != null) {
} else {
if (!isAwake() && allowWakeUpForCommands) {
logger.debug("Waking vehicle to send command.");
wakeUp();
@ -267,10 +273,16 @@ public class TeslaVehicleHandler extends BaseThingHandler {
setChargeLimit(0);
} else if (command instanceof IncreaseDecreaseType
&& command == IncreaseDecreaseType.INCREASE) {
setChargeLimit(Math.min(chargeState.charge_limit_soc + 1, 100));
ChargeState chargeState = this.chargeState;
if (chargeState != null) {
setChargeLimit(Math.min(chargeState.chargeLimitSoc + 1, 100));
}
} else if (command instanceof IncreaseDecreaseType
&& command == IncreaseDecreaseType.DECREASE) {
setChargeLimit(Math.max(chargeState.charge_limit_soc - 1, 0));
ChargeState chargeState = this.chargeState;
if (chargeState != null) {
setChargeLimit(Math.max(chargeState.chargeLimitSoc - 1, 0));
}
}
break;
}
@ -298,23 +310,17 @@ public class TeslaVehicleHandler extends BaseThingHandler {
break;
case COMBINED_TEMP: {
QuantityType<Temperature> quantity = commandToQuantityType(command);
if (quantity != null) {
setCombinedTemperature(quanityToRoundedFloat(quantity));
}
setCombinedTemperature(quanityToRoundedFloat(quantity));
break;
}
case DRIVER_TEMP: {
QuantityType<Temperature> quantity = commandToQuantityType(command);
if (quantity != null) {
setDriverTemperature(quanityToRoundedFloat(quantity));
}
setDriverTemperature(quanityToRoundedFloat(quantity));
break;
}
case PASSENGER_TEMP: {
QuantityType<Temperature> quantity = commandToQuantityType(command);
if (quantity != null) {
setPassengerTemperature(quanityToRoundedFloat(quantity));
}
setPassengerTemperature(quanityToRoundedFloat(quantity));
break;
}
case SENTRY_MODE: {
@ -411,11 +417,12 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
case RT: {
if (command instanceof OnOffType onOffCommand) {
VehicleState vehicleState = this.vehicleState;
if (onOffCommand == OnOffType.ON) {
if (vehicleState.rt == 0) {
if (vehicleState != null && vehicleState.rt == 0) {
openTrunk();
}
} else if (vehicleState.rt == 1) {
} else if (vehicleState != null && vehicleState.rt == 1) {
closeTrunk();
}
}
@ -459,9 +466,10 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
}
public void sendCommand(String command, String payLoad, WebTarget target) {
public void sendCommand(String command, @Nullable String payLoad, WebTarget target) {
if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
Request request = account.newRequest(this, command, payLoad, target, allowWakeUpForCommands);
QueueChannelThrottler stateThrottler = this.stateThrottler;
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
@ -475,6 +483,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
public void sendCommand(String command, String payLoad) {
if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
Request request = account.newRequest(this, command, payLoad, account.commandTarget, allowWakeUpForCommands);
QueueChannelThrottler stateThrottler = this.stateThrottler;
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
@ -484,16 +493,18 @@ public class TeslaVehicleHandler extends BaseThingHandler {
public void sendCommand(String command, WebTarget target) {
if (COMMAND_WAKE_UP.equals(command) || isAwake() || allowWakeUpForCommands) {
Request request = account.newRequest(this, command, "{}", target, allowWakeUpForCommands);
QueueChannelThrottler stateThrottler = this.stateThrottler;
if (stateThrottler != null) {
stateThrottler.submit(COMMAND_THROTTLE, request);
}
}
}
public void requestData(String command, String payLoad) {
public void requestData(String command, @Nullable String payLoad) {
if (COMMAND_WAKE_UP.equals(command) || isAwake()
|| (!"vehicleData".equals(command) && allowWakeUpForCommands)) {
Request request = account.newRequest(this, command, payLoad, account.dataRequestTarget, false);
QueueChannelThrottler stateThrottler = this.stateThrottler;
if (stateThrottler != null) {
stateThrottler.submit(DATA_THROTTLE, request);
}
@ -529,14 +540,16 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
protected boolean isAwake() {
return vehicle != null && "online".equals(vehicle.state) && vehicle.vehicle_id != null;
Vehicle vehicle = this.vehicle;
return vehicle != null && "online".equals(vehicle.state) && vehicle.vehicleId != null;
}
protected boolean isInMotion() {
DriveState driveState = this.driveState;
if (driveState != null) {
if (driveState.speed != null && driveState.shift_state != null) {
if (driveState.speed != null && driveState.shiftState != null) {
return !"Undefined".equals(driveState.speed)
&& (!"P".equals(driveState.shift_state) || !"Undefined".equals(driveState.shift_state));
&& (!"P".equals(driveState.shiftState) || !"Undefined".equals(driveState.shiftState));
}
}
return false;
@ -551,15 +564,16 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
protected boolean isCharging() {
return chargeState != null && "Charging".equals(chargeState.charging_state);
ChargeState chargeState = this.chargeState;
return chargeState != null && "Charging".equals(chargeState.chargingState);
}
protected boolean notReadyForSleep() {
boolean status;
int computedInactivityPeriod = inactivity;
VehicleState vehicleState = this.vehicleState;
if (useAdvancedStates) {
if (vehicleState.is_user_present && !isInMotion()) {
if (vehicleState != null && vehicleState.isUserPresent && !isInMotion()) {
logger.debug("Car is occupied but stationary.");
if (lastAdvModesTimestamp < (System.currentTimeMillis()
- (THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES * 60 * 1000))) {
@ -568,7 +582,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
return (backOffCounter++ % 6 == 0); // using 6 should make sure 1 out of 5 pollers get serviced,
// about every min.
}
} else if (vehicleState.sentry_mode) {
} else if (vehicleState != null && vehicleState.sentryMode) {
logger.debug("Car is in sentry mode.");
if (lastAdvModesTimestamp < (System.currentTimeMillis()
- (THRESHOLD_INTERVAL_FOR_ADVANCED_MINUTES * 60 * 1000))) {
@ -576,23 +590,23 @@ public class TeslaVehicleHandler extends BaseThingHandler {
} else {
return (backOffCounter++ % 6 == 0);
}
} else if ((vehicleState.center_display_state != 0) && (!isInMotion())) {
} else if (vehicleState != null && (vehicleState.centerDisplayState != 0) && (!isInMotion())) {
logger.debug("Car is in camp, climate keep, dog, or other mode preventing sleep. Mode {}",
vehicleState.center_display_state);
vehicleState.centerDisplayState);
return (backOffCounter++ % 6 == 0);
} else {
lastAdvModesTimestamp = System.currentTimeMillis();
}
}
if (vehicleState != null && vehicleState.homelink_nearby) {
if (vehicleState != null && vehicleState.homelinkNearby) {
computedInactivityPeriod = MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT;
logger.debug("Car is at home. Movement or drive state threshold is {} min.",
MOVE_THRESHOLD_INTERVAL_MINUTES_DEFAULT);
}
DriveState driveState = this.driveState;
if (useDriveState) {
if (driveState.shift_state != null) {
if (driveState != null && driveState.shiftState != null) {
logger.debug("Car drive state not null and not ready to sleep.");
return true;
} else {
@ -632,7 +646,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
lastLongitude = 0;
}
protected boolean checkResponse(Response response, boolean immediatelyFail) {
protected boolean checkResponse(@Nullable Response response, boolean immediatelyFail) {
if (response != null && response.getStatus() == 200) {
return true;
} else if (response != null && response.getStatus() == 401) {
@ -711,11 +725,13 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
public void setDriverTemperature(float temperature) {
setTemperature(temperature, climateState != null ? climateState.passenger_temp_setting : temperature);
ClimateState climateState = this.climateState;
setTemperature(temperature, climateState != null ? climateState.passengerTempSetting : temperature);
}
public void setPassengerTemperature(float temperature) {
setTemperature(climateState != null ? climateState.driver_temp_setting : temperature, temperature);
ClimateState climateState = this.climateState;
setTemperature(climateState != null ? climateState.passengerTempSetting : temperature, temperature);
}
public void openFrunk() {
@ -734,7 +750,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
openTrunk();
}
public void setValetMode(boolean b, Integer pin) {
public void setValetMode(boolean b, @Nullable Integer pin) {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("on", b);
if (pin != null) {
@ -785,7 +801,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
sendCommand(COMMAND_STEERING_WHEEL_HEATER, gson.toJson(payloadObject), account.commandTarget);
}
protected Vehicle queryVehicle() {
protected @Nullable Vehicle queryVehicle() {
String authHeader = account.getAuthHeader();
if (authHeader != null) {
@ -805,14 +821,17 @@ public class TeslaVehicleHandler extends BaseThingHandler {
JsonObject jsonObject = JsonParser.parseString(response.readEntity(String.class)).getAsJsonObject();
Vehicle[] vehicleArray = gson.fromJson(jsonObject.getAsJsonArray("response"), Vehicle[].class);
if (vehicleArray == null) {
logger.debug("Response resulted in unexpected null array");
return null;
}
for (Vehicle vehicle : vehicleArray) {
logger.debug("Querying the vehicle: VIN {}", vehicle.vin);
if (vehicle.vin.equals(getConfig().get(VIN))) {
vehicleJSON = gson.toJson(vehicle);
parseAndUpdate("queryVehicle", null, vehicleJSON);
if (logger.isTraceEnabled()) {
logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicle_id,
logger.trace("Vehicle is id {}/vehicle_id {}/tokens {}", vehicle.id, vehicle.vehicleId,
vehicle.tokens);
}
return vehicle;
@ -831,9 +850,9 @@ public class TeslaVehicleHandler extends BaseThingHandler {
vehicle = queryVehicle();
}
public void parseAndUpdate(String request, String payLoad, String result) {
public void parseAndUpdate(@Nullable String request, @Nullable String payLoad, @Nullable String result) {
final double locationThreshold = .0000001;
Vehicle vehicle = this.vehicle;
try {
if (request != null && result != null && !"null".equals(result)) {
updateStatus(ThingStatus.ONLINE);
@ -848,12 +867,12 @@ public class TeslaVehicleHandler extends BaseThingHandler {
return;
}
if (vehicle != null && ("asleep".equals(vehicle.state) || "offline".equals(vehicle.state))) {
if ("asleep".equals(vehicle.state) || "offline".equals(vehicle.state)) {
logger.debug("Vehicle is {}", vehicle.state);
return;
}
if (vehicle != null && !lastState.equals(vehicle.state)) {
if (!lastState.equals(vehicle.state)) {
lastState = vehicle.state;
// in case vehicle changed to awake, refresh all data
@ -888,43 +907,44 @@ public class TeslaVehicleHandler extends BaseThingHandler {
return;
}
driveState = vehicleData.drive_state;
if (Math.abs(lastLatitude - driveState.latitude) > locationThreshold
|| Math.abs(lastLongitude - driveState.longitude) > locationThreshold) {
DriveState driveState = this.driveState = vehicleData.driveState;
if (driveState != null && (Math.abs(lastLatitude - driveState.latitude) > locationThreshold
|| Math.abs(lastLongitude - driveState.longitude) > locationThreshold)) {
logger.debug("Vehicle moved, resetting last location timestamp");
lastLatitude = driveState.latitude;
lastLongitude = driveState.longitude;
lastLocationChangeTimestamp = System.currentTimeMillis();
}
logger.trace("Drive state: {}", driveState.shift_state);
logger.trace("Drive state: {}", driveState != null ? driveState.shiftState : "null");
if ((driveState.shift_state == null) && (lastValidDriveStateNotNull)) {
if ((driveState != null && driveState.shiftState == null) && (lastValidDriveStateNotNull)) {
logger.debug("Set NULL shiftstate time");
lastValidDriveStateNotNull = false;
lastDriveStateChangeToNullTimestamp = System.currentTimeMillis();
} else if (driveState.shift_state != null) {
} else if (driveState != null && driveState.shiftState != null) {
logger.trace("Clear NULL shiftstate time");
lastValidDriveStateNotNull = true;
}
guiState = vehicleData.gui_settings;
guiState = vehicleData.guiSettings;
vehicleState = vehicleData.vehicle_state;
VehicleState vehicleState = this.vehicleState = vehicleData.vehicleState;
chargeState = vehicleData.charge_state;
chargeState = vehicleData.chargeState;
if (isCharging()) {
updateState(CHANNEL_CHARGE, OnOffType.ON);
} else {
updateState(CHANNEL_CHARGE, OnOffType.OFF);
}
climateState = vehicleData.climate_state;
ClimateState climateState = this.climateState = vehicleData.climateState;
BigDecimal avgtemp = roundBigDecimal(new BigDecimal(
(climateState.driver_temp_setting + climateState.passenger_temp_setting) / 2.0f));
(climateState.passengerTempSetting + climateState.passengerTempSetting) / 2.0f));
updateState(CHANNEL_COMBINED_TEMP, new QuantityType<>(avgtemp, SIUnits.CELSIUS));
softwareUpdate = vehicleState.software_update;
SoftwareUpdate softwareUpdate = this.softwareUpdate = vehicleState.softwareUpdate;
try {
lock.lock();
@ -990,8 +1010,13 @@ public class TeslaVehicleHandler extends BaseThingHandler {
@SuppressWarnings("unchecked")
protected QuantityType<Temperature> commandToQuantityType(Command command) {
if (command instanceof QuantityType) {
return ((QuantityType<Temperature>) command).toUnit(SIUnits.CELSIUS);
if (command instanceof QuantityType quantityCommand) {
QuantityType<Temperature> commandInCelsius = quantityCommand.toUnit(SIUnits.CELSIUS);
if (commandInCelsius == null) {
logger.warn("Unable to convert command {} to CELSIUS", command);
} else {
return commandInCelsius;
}
}
return new QuantityType<>(new BigDecimal(command.toString()), SIUnits.CELSIUS);
}
@ -1024,18 +1049,19 @@ public class TeslaVehicleHandler extends BaseThingHandler {
};
protected Runnable eventRunnable = new Runnable() {
@Nullable
TeslaEventEndpoint eventEndpoint;
boolean isAuthenticated = false;
long lastPingTimestamp = 0;
@Override
public void run() {
eventEndpoint = new TeslaEventEndpoint(getThing().getUID(), webSocketFactory);
TeslaEventEndpoint eventEndpoint = new TeslaEventEndpoint(getThing().getUID(), webSocketFactory);
eventEndpoint.addEventHandler(new TeslaEventEndpoint.EventHandler() {
@Override
public void handleEvent(Event event) {
public void handleEvent(@Nullable Event event) {
if (event != null) {
switch (event.msg_type) {
switch (event.msgType) {
case "control:hello":
logger.debug("Event : Received hello");
break;
@ -1068,7 +1094,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
if (!selector.isProperty()) {
State newState = teslaChannelSelectorProxy.getState(vals[i], selector,
editProperties());
if (newState != null && !"".equals(vals[i])) {
if (!"".equals(vals[i])) {
updateState(selector.getChannelID(), newState);
} else {
updateState(selector.getChannelID(), UnDefType.UNDEF);
@ -1115,29 +1141,28 @@ public class TeslaVehicleHandler extends BaseThingHandler {
}
break;
case "data:error":
logger.debug("Event : Received an error: '{}'/'{}'", event.value, event.error_type);
logger.debug("Event : Received an error: '{}'/'{}'", event.value, event.errorType);
eventEndpoint.closeConnection();
break;
}
}
}
});
this.eventEndpoint = eventEndpoint;
while (true) {
try {
if (getThing().getStatus() == ThingStatus.ONLINE) {
if (isAwake()) {
eventEndpoint.connect(new URI(URI_EVENT));
if (eventEndpoint.isConnected()) {
if (!isAuthenticated) {
logger.debug("Event : Authenticating vehicle {}", vehicle.vehicle_id);
logger.debug("Event : Authenticating vehicle {}", vehicle.vehicleId);
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("msg_type", "data:subscribe_oauth");
payloadObject.addProperty("token", account.getAccessToken());
payloadObject.addProperty("value", Arrays.asList(EventKeys.values()).stream()
.skip(1).map(Enum::toString).collect(Collectors.joining(",")));
payloadObject.addProperty("tag", vehicle.vehicle_id);
payloadObject.addProperty("tag", vehicle.vehicleId);
eventEndpoint.sendMessage(gson.toJson(payloadObject));
isAuthenticated = true;
@ -1200,6 +1225,7 @@ public class TeslaVehicleHandler extends BaseThingHandler {
if (Thread.interrupted()) {
logger.debug("Event : The event thread was interrupted");
eventEndpoint.close();
this.eventEndpoint = eventEndpoint;
return;
}
}

View File

@ -14,8 +14,8 @@ package org.openhab.binding.tesla.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.tesla.internal.protocol.Vehicle;
import org.openhab.binding.tesla.internal.protocol.VehicleConfig;
import org.openhab.binding.tesla.internal.protocol.dto.Vehicle;
import org.openhab.binding.tesla.internal.protocol.dto.VehicleConfig;
/**
* The {@link VehicleListener} interface can be implemented by classes that want to be informed about

View File

@ -1,69 +0,0 @@
/**
* Copyright (c) 2010-2024 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_amps;
public int charge_current_request;
public int charge_current_request_max;
public int charge_limit_soc;
public int charge_limit_soc_max;
public int charge_limit_soc_min;
public int charge_limit_soc_std;
public int charger_actual_current;
public int charger_phases;
public int charger_pilot_current;
public int charger_power;
public int charger_voltage;
public int max_range_charge_counter;
public int usable_battery_level;
public String charge_port_latch;
public String charging_state;
public String conn_charge_cable;
public String fast_charger_brand;
public String fast_charger_type;
public String managed_charging_start_time;
public String scheduled_charging_start_time;
public String user_charge_enable_request;
ChargeState() {
}
}

View File

@ -1,53 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
/**
* The {@link ClimateState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class ClimateState {
public boolean battery_heater;
public boolean battery_heater_no_power;
public boolean is_auto_conditioning_on;
public boolean is_climate_on;
public boolean is_front_defroster_on;
public boolean is_preconditioning;
public boolean is_rear_defroster_on;
public int seat_heater_left;
public int seat_heater_rear_center;
public int seat_heater_rear_left;
public int seat_heater_rear_right;
public int seat_heater_right;
public boolean side_mirror_heaters;
public boolean smart_preconditioning;
public boolean steering_wheel_heater;
public boolean wiper_blade_heater;
public float driver_temp_setting;
public float inside_temp;
public float outside_temp;
public float passenger_temp_setting;
public int fan_status;
public int left_temp_direction;
public int max_avail_temp;
public int min_avail_temp;
public int right_temp_direction;
public int seat_heater_rear_left_back;
public int seat_heater_rear_right_back;
ClimateState() {
}
}

View File

@ -1,42 +0,0 @@
/**
* Copyright (c) 2010-2024 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 String active_route_destination;
public double active_route_latitude;
public double active_route_longitude;
public double active_route_miles_to_arrival;
public double active_route_minutes_to_arrival;
public double active_route_traffic_minutes_delay;
public double latitude;
public double longitude;
public double native_latitude;
public double native_longitude;
public int gps_as_of;
public int heading;
public int native_location_supported;
public String native_type;
public String shift_state;
public String speed;
DriveState() {
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
/**
* The {@link GUIState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class GUIState {
public String gui_distance_units;
public String gui_temperature_units;
public String gui_charge_rate_units;
public String gui_24_hour_time;
public String gui_range_display;
public GUIState() {
}
}

View File

@ -1,39 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
/**
* The {@link Vehicle} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class Vehicle {
public String color;
public String display_name;
public String id;
public String option_codes;
public String vehicle_id;
public String vin;
public String[] tokens;
public String state;
public boolean remote_start_enabled;
public boolean calendar_enabled;
public boolean notifications_enabled;
public String backseat_token;
public String backseat_token_updated_at;
Vehicle() {
}
}

View File

@ -1,63 +0,0 @@
/**
* Copyright (c) 2010-2024 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 org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link VehicleConfig} is a data structure to capture
* vehicle configuration variables sent by the Tesla Vehicle
*
* @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;
public ThingTypeUID identifyModel() {
switch (car_type) {
case "models":
case "models2":
return TeslaBindingConstants.THING_TYPE_MODELS;
case "modelx":
return TeslaBindingConstants.THING_TYPE_MODELX;
case "model3":
return TeslaBindingConstants.THING_TYPE_MODEL3;
case "modely":
return TeslaBindingConstants.THING_TYPE_MODELY;
default:
return TeslaBindingConstants.THING_TYPE_VEHICLE;
}
}
}

View File

@ -1,46 +0,0 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
/**
* The {@link VehicleData} is a data structure to capture
* variables sent by the Tesla API about a vehicle.
*
* @author Kai Kreuzer - Initial contribution
*/
public class VehicleData {
public String color;
public String display_name;
public String id;
public String option_codes;
public String vehicle_id;
public String vin;
public String[] tokens;
public String state;
public boolean remote_start_enabled;
public boolean calendar_enabled;
public boolean notifications_enabled;
public String backseat_token;
public String backseat_token_updated_at;
public ChargeState charge_state;
public ClimateState climate_state;
public DriveState drive_state;
public GUIState gui_settings;
public VehicleConfig vehicle_config;
public VehicleState vehicle_state;
VehicleData() {
}
}

View File

@ -1,64 +0,0 @@
/**
* Copyright (c) 2010-2024 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 is_user_present;
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 sentry_mode;
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;
public SoftwareUpdate software_update;
VehicleState() {
}
}

View File

@ -0,0 +1,115 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ChargeState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class ChargeState {
@SerializedName("battery_heater_on")
public boolean batteryHeaterOn;
@SerializedName("charge_enable_request")
public boolean chargeEnableRequest;
@SerializedName("charge_port_door_open")
public boolean chargePortDoorOpen;
@SerializedName("charge_to_max_range")
public boolean chargeToMaxRange;
@SerializedName("eu_vehicle")
public boolean euVehicle;
@SerializedName("fast_charger_present")
public boolean fastChargerPresent;
@SerializedName("managed_charging_active")
public boolean managedChargingActive;
@SerializedName("managed_charging_user_canceled")
public boolean managedChargingUserCanceled;
@SerializedName("motorized_charge_port")
public boolean motorizedChargePort;
@SerializedName("not_enough_power_to_heat")
public boolean notEnoughPowerToHeat;
@SerializedName("scheduled_charging_pending")
public boolean scheduledChargingPending;
@SerializedName("trip_charging")
public boolean tripCharging;
@SerializedName("battery_current")
public float batteryCurrent;
@SerializedName("battery_range")
public float batteryRange;
@SerializedName("charge_energy_added")
public float chargeEnergyAdded;
@SerializedName("charge_miles_added_ideal")
public float chargeMilesAddedIdeal;
@SerializedName("charge_miles_added_rated")
public float chargeMilesAddedRated;
@SerializedName("aaacharge_rateaa")
public float chargeRate;
@SerializedName("est_battery_range")
public float estBatteryRange;
@SerializedName("ideal_battery_range")
public float idealBatteryRange;
@SerializedName("time_to_full_charge")
public float timeToFullCharge;
@SerializedName("battery_level")
public int batteryLevel;
@SerializedName("charge_amps")
public int chargeAmps;
@SerializedName("charge_current_request")
public int chargeCurrentRequest;
@SerializedName("charge_current_request_max")
public int chargeCurrentRequestMax;
@SerializedName("charge_limit_soc")
public int chargeLimitSoc;
@SerializedName("charge_limit_soc_max")
public int chargeLimitSocMax;
@SerializedName("charge_limit_soc_min")
public int chargeLimitSocMin;
@SerializedName("charge_limit_soc_std")
public int chargeLimitSocStd;
@SerializedName("charger_actual_current")
public int chargerActualCurrent;
@SerializedName("charger_phases")
public int chargerPhases;
@SerializedName("charger_pilot_current")
public int chargerPilotCurrent;
@SerializedName("charger_power")
public int chargerPower;
@SerializedName("charger_voltage")
public int chargerVoltage;
@SerializedName("max_range_charge_counter")
public int maxRangeChargeCounter;
@SerializedName("usable_battery_level")
public int usableBatteryLevel;
@SerializedName("charge_port_latch")
public String chargePortLatch;
@SerializedName("charging_state")
public String chargingState;
@SerializedName("conn_charge_cable")
public String connChargeCable;
@SerializedName("fast_charger_brand")
public String fastChargerBrand;
@SerializedName("fast_charger_type")
public String fastChargerType;
@SerializedName("managed_charging_start_time")
public String managedChargingStartTime;
@SerializedName("scheduled_charging_start_time")
public String scheduledChargingStartTime;
@SerializedName("user_charge_enable_request")
public String userChargeEnableRequest;
ChargeState() {
}
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link ClimateState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class ClimateState {
@SerializedName("allow_cabin_overheat_protection")
public boolean allowCabinOverheatProtection;
@SerializedName("auto_seat_climate_left")
public boolean autoSeatClimateLeft;
@SerializedName("auto_seat_climate_right")
public boolean autoSeatClimateRight;
@SerializedName("battery_heater")
public boolean batteryHeater;
@SerializedName("battery_heater_no_power")
public boolean batteryHeaterNoPower;
@SerializedName("cabin_overheat_protection")
public String cabinOverheatProtection;
@SerializedName("cabin_overheat_protection_actively_cooling")
public boolean cabinOverheatProtectionActivelyCooling;
@SerializedName("climate_keeper_mode")
public String climateKeeperMode;
@SerializedName("cop_activation_temperature")
public String copActivationTemperature;
@SerializedName("defrost_mode")
public int defrostMode;
@SerializedName("driver_temp_setting")
public float driverTempSetting;
@SerializedName("fan_status")
public int fanStatus;
@SerializedName("hvac_auto_request")
public String hvacAutoRequest;
@SerializedName("inside_temp")
public float insideTemp;
@SerializedName("is_auto_conditioning_on")
public boolean isAutoConditioningOn;
@SerializedName("is_climate_on")
public boolean isClimateOn;
@SerializedName("is_front_defroster_on")
public boolean isFrontDefrosterOn;
@SerializedName("is_preconditioning")
public boolean isPreconditioning;
@SerializedName("is_rear_defroster_on")
public boolean isRearDefrosterOn;
@SerializedName("left_temp_direction")
public int leftTempDirection;
@SerializedName("max_avail_temp")
public float maxAvailTemp;
@SerializedName("min_avail_temp")
public float minAvailTemp;
@SerializedName("outside_temp")
public float outsideTemp;
@SerializedName("passenger_temp_setting")
public float passengerTempSetting;
@SerializedName("remote_heater_control_enabled")
public boolean remoteHeaterControlEnabled;
@SerializedName("right_temp_direction")
public int rightTempDirection;
@SerializedName("seat_heater_left")
public int seatHeaterLeft;
@SerializedName("seat_heater_right")
public int seatHeaterRight;
@SerializedName("side_mirror_heaters")
public boolean sideMirrorHeaters;
@SerializedName("supports_fan_only_cabin_overheat_protection")
public boolean supportsFanOnlyCabinOverheatProtection;
@SerializedName("timestamp")
public long timestamp;
@SerializedName("wiper_blade_heater")
public boolean wiperBladeHeater;
ClimateState() {
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link DriveState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class DriveState {
@SerializedName("gps_as_of")
public int gpsAsOf;
@SerializedName("heading")
public int heading;
@SerializedName("latitude")
public double latitude;
@SerializedName("longitude")
public double longitude;
@SerializedName("native_latitude")
public double nativeLatitude;
@SerializedName("native_location_supported")
public int nativeLocationSupported;
@SerializedName("native_longitude")
public double nativeLongitude;
@SerializedName("native_type")
public String nativeType;
@SerializedName("power")
public int power;
@SerializedName("shift_state")
public String shiftState;
@SerializedName("speed")
public String speed;
@SerializedName("timestamp")
public long timestamp;
DriveState() {
}
}

View File

@ -10,7 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
package org.openhab.binding.tesla.internal.protocol.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link Event} is a datastructure to capture
@ -19,9 +21,11 @@ package org.openhab.binding.tesla.internal.protocol;
* @author Karel Goderis - Initial contribution
*/
public class Event {
public String msg_type;
@SerializedName("msg_type")
public String msgType;
public String value;
public String tag;
public String error_type;
@SerializedName("error_type")
public String errorType;
public int connectionTimeout;
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link GUIState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class GUIState {
@SerializedName("gui_24_hour_time")
public boolean gui24HourTime;
@SerializedName("gui_charge_rate_units")
public String guiChargeRateUnits;
@SerializedName("gui_distance_units")
public String guiDistanceUnits;
@SerializedName("gui_range_display")
public String guiRangeDisplay;
@SerializedName("gui_temperature_units")
public String guiTemperatureUnits;
@SerializedName("show_range_units")
public boolean showRangeUnits;
@SerializedName("timestamp")
public Long timestamp;
public GUIState() {
}
}

View File

@ -10,7 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol;
package org.openhab.binding.tesla.internal.protocol.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SoftwareUpdate} is a datastructure to capture
@ -20,9 +22,12 @@ package org.openhab.binding.tesla.internal.protocol;
*/
public class SoftwareUpdate {
public int download_perc;
public int expected_duration_sec;
public int install_perc;
@SerializedName("download_perc")
public int downloadPerc;
@SerializedName("expected_duration_sec")
public int expectedDurationSec;
@SerializedName("install_perc")
public int installPerc;
public String status;
public String version;

View File

@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* 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;
@SerializedName("display_name")
public String displayName;
public String id;
@SerializedName("option_codes")
public String optionCodes;
@SerializedName("vehicle_id")
public String vehicleId;
public String vin;
public String[] tokens;
public String state;
@SerializedName("remote_start_enabled")
public boolean remoteStartEnabled;
@SerializedName("calendar_enabled")
public boolean calendarEnabled;
@SerializedName("notifications_enabled")
public boolean notificationsEnabled;
@SerializedName("backseat_token")
public String backseatToken;
@SerializedName("backseat_token_updated_at")
public String backseatTokenUpdatedAt;
Vehicle() {
}
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2024 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.dto;
import org.openhab.binding.tesla.internal.TeslaBindingConstants;
import org.openhab.core.thing.ThingTypeUID;
import com.google.gson.annotations.SerializedName;
/**
* 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 {
@SerializedName("can_accept_navigation_requests")
public boolean canAcceptNavigationRequests;
@SerializedName("can_actuate_trunks")
public boolean canActuateTrunks;
@SerializedName("car_special_type")
public String carSpecialType;
@SerializedName("car_type")
public String carType;
@SerializedName("charge_port_type")
public String chargePortType;
@SerializedName("ece_restrictions")
public boolean eceRestrictions;
@SerializedName("eu_vehicle")
public boolean euVehicle;
@SerializedName("exterior_color")
public String exteriorColor;
@SerializedName("has_air_suspension")
public boolean hasAirSuspension;
@SerializedName("has_ludicrous_mode")
public boolean hasLudicrousMode;
@SerializedName("motorized_charge_port")
public boolean motorizedChargePort;
@SerializedName("plg")
public boolean plg;
@SerializedName("rear_seat_heaters")
public int rearSeatHeaters;
@SerializedName("rear_seat_type")
public int rearSeatType;
@SerializedName("rhd")
public boolean rhd;
@SerializedName("roof_color")
public String roofColor;
@SerializedName("seat_type")
public int seatType;
@SerializedName("spoiler_type")
public String spoilerType;
@SerializedName("sun_roof_installed")
public int sunRoofInstalled;
@SerializedName("third_row_seats")
public String thirdRowSeats;
@SerializedName("timestamp")
public Long timestamp;
@SerializedName("trim_badging")
public String trimBadging;
@SerializedName("use_range_badging")
public boolean useRangeBadging;
@SerializedName("wheel_type")
public String wheelType;
public ThingTypeUID identifyModel() {
switch (carType) {
case "models":
case "models2":
return TeslaBindingConstants.THING_TYPE_MODELS;
case "modelx":
return TeslaBindingConstants.THING_TYPE_MODELX;
case "model3":
return TeslaBindingConstants.THING_TYPE_MODEL3;
case "modely":
return TeslaBindingConstants.THING_TYPE_MODELY;
default:
return TeslaBindingConstants.THING_TYPE_VEHICLE;
}
}
}

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2024 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.dto;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link VehicleData} is a data structure to capture
* variables sent by the Tesla API about a vehicle.
*
* @author Kai Kreuzer - Initial contribution
*/
public class VehicleData {
@SerializedName("id")
public Long id;
@SerializedName("user_id")
public int userId;
@SerializedName("vehicle_id")
public String vehicleId;
@SerializedName("vin")
public String vin;
@SerializedName("display_name")
public String displayName;
@SerializedName("color")
public Object color;
@SerializedName("access_type")
public String accessType;
@SerializedName("tokens")
public List<String> tokens;
@SerializedName("state")
public String state;
@SerializedName("in_service")
public boolean inService;
@SerializedName("id_s")
public String idS;
@SerializedName("calendar_enabled")
public boolean calendarEnabled;
@SerializedName("api_version")
public int apiVersion;
@SerializedName("backseat_token")
public Object backseatToken;
@SerializedName("backseat_token_updated_at")
public Object backseatTokenUpdatedAt;
@SerializedName("charge_state")
public ChargeState chargeState;
@SerializedName("climate_state")
public ClimateState climateState;
@SerializedName("drive_state")
public DriveState driveState;
@SerializedName("gui_settings")
public GUIState guiSettings;
@SerializedName("vehicle_config")
public VehicleConfig vehicleConfig;
@SerializedName("vehicle_state")
public VehicleState vehicleState;
VehicleData() {
}
}

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2024 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.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link VehicleState} is a datastructure to capture
* variables sent by the Tesla Vehicle
*
* @author Karel Goderis - Initial contribution
*/
public class VehicleState {
@SerializedName("dark_rims")
public boolean darkRims;
@SerializedName("has_spoiler")
public boolean hasSpoiler;
@SerializedName("homelink_nearby")
public boolean homelinkNearby;
@SerializedName("is_user_present")
public boolean isUserPresent;
public boolean locked;
@SerializedName("notifications_supported")
public boolean notificationsSupported;
@SerializedName("parsed_calendar_supported")
public boolean parsedCalendarSupported;
@SerializedName("remote_start")
public boolean remoteStart;
@SerializedName("remote_start_supported")
public boolean remoteStartSupported;
public boolean rhd;
@SerializedName("sentry_mode")
public boolean sentryMode;
@SerializedName("valet_mode")
public boolean valetMode;
@SerializedName("valet_pin_needed")
public boolean valetPinNeeded;
public float odometer;
@SerializedName("center_display_state")
public int centerDisplayState;
public int df;
public int dr;
public int ft;
public int pf;
public int pr;
@SerializedName("rear_seat_heaters")
public int rearSeatHeaters;
public int rt;
@SerializedName("seat_type")
public int seatType;
@SerializedName("sun_roof_installed")
public int sunRoofInstalled;
@SerializedName("sun_roof_percent_open")
public int sunRoofPercentOpen;
@SerializedName("autopark_state")
public String autoparkState;
@SerializedName("autopark_state_v2")
public String autoparkStateV2;
@SerializedName("autopark_style")
public String autoparkStyle;
@SerializedName("car_version")
public String carVersion;
@SerializedName("exterior_color")
public String exteriorColor;
@SerializedName("last_autopark_error")
public String lastAutoparkError;
@SerializedName("perf_config")
public String perfConfig;
@SerializedName("roof_color")
public String roofColor;
@SerializedName("sun_roof_state")
public String sunRoofState;
@SerializedName("vehicle_name")
public String vehicleName;
@SerializedName("wheel_type")
public String wheelType;
@SerializedName("software_update")
public SoftwareUpdate softwareUpdate;
VehicleState() {
}
}

View File

@ -10,10 +10,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol.sso;
package org.openhab.binding.tesla.internal.protocol.dto.sso;
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
import com.google.gson.annotations.SerializedName;
/**
* The {@link AuthorizationCodeExchangeRequest} is a datastructure to exchange
* the authorization code for an access token on the SSO endpoint
@ -22,14 +24,18 @@ import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
*/
@SuppressWarnings("unused") // Unused fields must not be removed since they are used for serialization to JSON
public class AuthorizationCodeExchangeRequest {
private String grant_type = "authorization_code";
private String client_id = CLIENT_ID;
@SerializedName("grant_type")
private String grantType = "authorization_code";
@SerializedName("client_id")
private String clientId = CLIENT_ID;
private String code;
private String code_verifier;
private String redirect_uri = URI_CALLBACK;
@SerializedName("code_verifier")
private String codeVerifier;
@SerializedName("redirect_uri")
private String redirectUri = URI_CALLBACK;
public AuthorizationCodeExchangeRequest(String code, String codeVerifier) {
this.code = code;
this.code_verifier = codeVerifier;
this.codeVerifier = codeVerifier;
}
}

View File

@ -10,7 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol.sso;
package org.openhab.binding.tesla.internal.protocol.dto.sso;
import com.google.gson.annotations.SerializedName;
/**
* The {@link AuthorizationCodeExchangeResponse} is a datastructure to capture
@ -19,9 +21,13 @@ package org.openhab.binding.tesla.internal.protocol.sso;
* @author Christian Güdel - Initial contribution
*/
public class AuthorizationCodeExchangeResponse {
public String access_token;
public String refresh_token;
public String expires_in;
@SerializedName("access_token")
public String accessToken;
@SerializedName("refresh_token")
public String refreshToken;
@SerializedName("expires_in")
public String expiresIn;
public String state;
public String token_type;
@SerializedName("token_type")
public String tokenType;
}

View File

@ -10,10 +10,12 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol.sso;
package org.openhab.binding.tesla.internal.protocol.dto.sso;
import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
import com.google.gson.annotations.SerializedName;
/**
* The {@link RefreshTokenRequest} is a datastructure to refresh
* the access token for the SSO endpoint
@ -21,12 +23,15 @@ import static org.openhab.binding.tesla.internal.TeslaBindingConstants.*;
* @author Christian Güdel - Initial contribution
*/
public class RefreshTokenRequest {
public String grant_type = "refresh_token";
public String client_id = CLIENT_ID;
public String refresh_token;
@SerializedName("grant_type")
public String grantType = "refresh_token";
@SerializedName("client_id")
public String clientId = CLIENT_ID;
@SerializedName("refresh_token")
public String refreshToken;
public String scope = SSO_SCOPES;
public RefreshTokenRequest(String refresh_token) {
this.refresh_token = refresh_token;
public RefreshTokenRequest(String refreshToken) {
this.refreshToken = refreshToken;
}
}

View File

@ -10,7 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol.sso;
package org.openhab.binding.tesla.internal.protocol.dto.sso;
import com.google.gson.annotations.SerializedName;
/**
* The {@link TokenExchangeRequest} is a datastructure to exchange
@ -19,7 +21,10 @@ package org.openhab.binding.tesla.internal.protocol.sso;
* @author Christian Güdel - Initial contribution
*/
public class TokenExchangeRequest {
public String grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer";
public String client_id = "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384";
public String client_secret = "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3";
@SerializedName("grant_type")
public String grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer";
@SerializedName("client_id")
public String clientId = "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384";
@SerializedName("client_secret")
public String clientSecret = "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3";
}

View File

@ -10,7 +10,9 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.tesla.internal.protocol.sso;
package org.openhab.binding.tesla.internal.protocol.dto.sso;
import com.google.gson.annotations.SerializedName;
/**
* The {@link TokenResponse} is a datastructure to capture
@ -19,12 +21,16 @@ package org.openhab.binding.tesla.internal.protocol.sso;
* @author Nicolai Grødum - Initial contribution
*/
public class TokenResponse {
public String access_token;
public String token_type;
public Long expires_in;
public Long created_at;
public String refresh_token;
@SerializedName("access_token")
public String accessToken;
@SerializedName("token_type")
public String tokenType;
@SerializedName("expires_in")
public Long expiresIn;
@SerializedName("created_at")
public Long createdAt;
@SerializedName("refresh_token")
public String refreshToken;
public TokenResponse() {
}

View File

@ -16,12 +16,16 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link AbstractChannelThrottler} is abstract class implementing a
* throttler with one global execution rate, or rate limiter
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
abstract class AbstractChannelThrottler implements ChannelThrottler {
protected final Rate totalRate;
@ -37,7 +41,7 @@ abstract class AbstractChannelThrottler implements ChannelThrottler {
this.timeProvider = timeProvider;
}
protected synchronized long callTime(Rate channel) {
protected synchronized long callTime(@Nullable Rate channel) {
long now = timeProvider.getCurrentTimeInMillis();
long callTime = totalRate.callTime(now);
if (channel != null) {

View File

@ -18,12 +18,16 @@ import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link AbstractMultiRateChannelThrottler} is abstract class implementing
* a throttler with multiple global execution rates, or rate limiters
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
abstract class AbstractMultiRateChannelThrottler implements ChannelThrottler {
protected final TimeProvider timeProvider;
@ -43,7 +47,7 @@ abstract class AbstractMultiRateChannelThrottler implements ChannelThrottler {
this.rates.add(rate);
}
protected synchronized long callTime(Rate channel) {
protected synchronized long callTime(@Nullable Rate channel) {
long maxCallTime = 0;
long finalCallTime = 0;
long now = timeProvider.getCurrentTimeInMillis();

View File

@ -14,14 +14,20 @@ package org.openhab.binding.tesla.internal.throttler;
import java.util.concurrent.Future;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ChannelThrottler} defines the interface for to submit tasks to a
* throttler
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public interface ChannelThrottler {
@Nullable
Future<?> submit(Runnable task);
@Nullable
Future<?> submit(Object channelKey, Runnable task);
}

View File

@ -22,6 +22,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,6 +33,7 @@ import org.slf4j.LoggerFactory;
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public final class QueueChannelThrottler extends AbstractMultiRateChannelThrottler {
private final Logger logger = LoggerFactory.getLogger(QueueChannelThrottler.class);
@ -71,13 +74,13 @@ public final class QueueChannelThrottler extends AbstractMultiRateChannelThrottl
}
@Override
public Future<?> submit(Runnable task) {
public @Nullable Future<?> submit(Runnable task) {
return submit(null, task);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Future<?> submit(Object channelKey, Runnable task) {
public @Nullable Future<?> submit(@Nullable Object channelKey, Runnable task) {
FutureTask runTask = new FutureTask(task, null);
try {
if (tasks.offer(runTask, overallRate.timeInMillis(), TimeUnit.MILLISECONDS)) {

View File

@ -17,6 +17,8 @@ import java.util.LinkedList;
import java.util.ListIterator;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* 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
@ -24,6 +26,7 @@ import java.util.concurrent.TimeUnit;
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public final class Rate {
private final int numberCalls;

View File

@ -19,6 +19,9 @@ import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link ScheduledChannelThrottler} implements a throttler that maintains a
* single execution rates, and does not maintain order of calls (thus has to
@ -26,6 +29,7 @@ import java.util.concurrent.TimeUnit;
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public final class ScheduledChannelThrottler extends AbstractChannelThrottler {
public ScheduledChannelThrottler(Rate totalRate) {
@ -53,13 +57,13 @@ public final class ScheduledChannelThrottler extends AbstractChannelThrottler {
}
@Override
public Future<?> submit(Runnable task) {
public @Nullable 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) {
public @Nullable Future<?> submit(Object channelKey, Runnable task) {
return scheduler.schedule(task, getThrottleDelay(channelKey), TimeUnit.MILLISECONDS);
}
}

View File

@ -12,11 +12,14 @@
*/
package org.openhab.binding.tesla.internal.throttler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link TimeProvider} provides time stamps
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public interface TimeProvider {
static final TimeProvider SYSTEM_PROVIDER = new TimeProvider() {
@Override