mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
* [mybmw] add stop charging command * [mybmw] fix hcaptcha issue (#17862) Signed-off-by: Martin Grassl <martin.grassl@digital-filestore.de> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
dcf2a1aa6a
commit
1fd1ac5fd9
@ -78,11 +78,12 @@ Properties will be attached to predefined vehicles if the VIN is matching.
|
||||
|
||||
### Bridge Configuration
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|--------------------------------------------------------------------|
|
||||
| userName | text | MyBMW Username |
|
||||
| password | text | MyBMW Password |
|
||||
| region | text | Select region in order to connect to the appropriate BMW server. |
|
||||
| Parameter | Type | Description |
|
||||
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
|
||||
| userName | text | MyBMW Username |
|
||||
| password | text | MyBMW Password |
|
||||
| hcaptchatoken | text | HCaptcha-Token for initial login (see https://bimmer-connected.readthedocs.io/en/latest/captcha.html) |
|
||||
| region | text | Select region in order to connect to the appropriate BMW server. |
|
||||
|
||||
The region Configuration has 3 different options
|
||||
|
||||
@ -849,4 +850,4 @@ sitemap BMW label="BMW" {
|
||||
|
||||
## Credits
|
||||
|
||||
This work is based on the project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
|
||||
This work is based on the great work of the project of [Bimmer Connected](https://github.com/bimmerconnected/bimmer_connected).
|
||||
|
@ -19,7 +19,7 @@ import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
* The {@link MyBMWBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - renamed
|
||||
* @author Martin Grassl - renamed and added hcaptchastring
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWBridgeConfiguration {
|
||||
@ -27,20 +27,71 @@ public class MyBMWBridgeConfiguration {
|
||||
/**
|
||||
* Depending on the location the correct server needs to be called
|
||||
*/
|
||||
public String region = Constants.EMPTY;
|
||||
private String region = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* MyBMW App Username
|
||||
*/
|
||||
public String userName = Constants.EMPTY;
|
||||
private String userName = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* MyBMW App Password
|
||||
*/
|
||||
public String password = Constants.EMPTY;
|
||||
private String password = Constants.EMPTY;
|
||||
|
||||
/**
|
||||
* Preferred Locale language
|
||||
*/
|
||||
public String language = Constants.LANGUAGE_AUTODETECT;
|
||||
private String language = Constants.LANGUAGE_AUTODETECT;
|
||||
|
||||
/**
|
||||
* the hCaptcha string
|
||||
*/
|
||||
private String hcaptchatoken = Constants.EMPTY;
|
||||
|
||||
public String getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public void setRegion(String region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getHcaptchatoken() {
|
||||
return hcaptchatoken;
|
||||
}
|
||||
|
||||
public void setHcaptchatoken(String hcaptchatoken) {
|
||||
this.hcaptchatoken = hcaptchatoken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MyBMWBridgeConfiguration [region=" + region + ", userName=" + userName + ", password=" + password
|
||||
+ ", language=" + language + ", hcaptchatoken=" + hcaptchatoken + "]";
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
@ -140,7 +139,7 @@ public class MyBMWCommandExtension extends AbstractConsoleCommandExtension imple
|
||||
String accountPath = path + File.separator + "Account-" + String.valueOf(accountNdx);
|
||||
handler.getMyBmwProxy().ifPresentOrElse(prox -> {
|
||||
// get list of vehicles
|
||||
List<@NonNull VehicleBase> vehicles = null;
|
||||
List<VehicleBase> vehicles = null;
|
||||
try {
|
||||
vehicles = prox.requestVehiclesBase();
|
||||
|
||||
@ -314,7 +313,8 @@ public class MyBMWCommandExtension extends AbstractConsoleCommandExtension imple
|
||||
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID())
|
||||
&& args[1].equals(t.getConfiguration().get("userName")))
|
||||
.map(t -> t.getHandler()).findAny().get();
|
||||
List<VehicleBase> vehicles = handler.getMyBmwProxy().get().requestVehiclesBase();
|
||||
List<VehicleBase> vehicles = handler != null ? handler.getMyBmwProxy().get().requestVehiclesBase()
|
||||
: List.of();
|
||||
return new StringsCompleter(
|
||||
vehicles.stream().map(v -> v.getVin()).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);
|
||||
|
@ -17,7 +17,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWConstants;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
@ -80,7 +79,7 @@ public class VehicleDiscovery extends AbstractThingHandlerDiscoveryService<MyBMW
|
||||
myBMWProxy = thingHandler.getMyBmwProxy();
|
||||
|
||||
try {
|
||||
Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
|
||||
Optional<List<Vehicle>> vehicleList = myBMWProxy.map(prox -> {
|
||||
try {
|
||||
return prox.requestVehicles();
|
||||
} catch (NetworkException e) {
|
||||
@ -88,8 +87,13 @@ public class VehicleDiscovery extends AbstractThingHandlerDiscoveryService<MyBMW
|
||||
}
|
||||
});
|
||||
vehicleList.ifPresentOrElse(vehicles -> {
|
||||
thingHandler.vehicleDiscoverySuccess();
|
||||
processVehicles(vehicles);
|
||||
if (vehicles.size() > 0) {
|
||||
thingHandler.vehicleDiscoverySuccess();
|
||||
processVehicles(vehicles);
|
||||
} else {
|
||||
logger.warn("no vehicle found, maybe because of network error");
|
||||
thingHandler.vehicleDiscoveryError();
|
||||
}
|
||||
}, () -> thingHandler.vehicleDiscoveryError());
|
||||
} catch (IllegalStateException ex) {
|
||||
thingHandler.vehicleDiscoveryError();
|
||||
|
@ -20,17 +20,27 @@ import com.google.gson.annotations.SerializedName;
|
||||
* The {@link AuthResponse} Data Transfer Object
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted from myBmwProxy
|
||||
*/
|
||||
public class AuthResponse {
|
||||
@SerializedName("access_token")
|
||||
public String accessToken = Constants.EMPTY;
|
||||
|
||||
@SerializedName("refresh_token")
|
||||
public String refreshToken = Constants.EMPTY;
|
||||
|
||||
@SerializedName("token_type")
|
||||
public String tokenType = Constants.EMPTY;
|
||||
|
||||
@SerializedName("gcid")
|
||||
public String gcid = Constants.EMPTY;
|
||||
|
||||
@SerializedName("expires_in")
|
||||
public int expiresIn = -1;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Token " + accessToken + " type " + tokenType + " expires in " + expiresIn;
|
||||
return "AuthResponse [accessToken=" + accessToken + ", refreshToken=" + refreshToken + ", tokenType="
|
||||
+ tokenType + ", gcid=" + gcid + ", expiresIn=" + expiresIn + "]";
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
|
||||
private Optional<VehicleDiscovery> vehicleDiscovery = Optional.empty();
|
||||
private LocaleProvider localeProvider;
|
||||
private Optional<MyBMWBridgeConfiguration> bmwBridgeConfiguration = Optional.empty();
|
||||
|
||||
public MyBMWBridgeHandler(Bridge bridge, HttpClientFactory hcf, LocaleProvider localeProvider) {
|
||||
super(bridge);
|
||||
@ -82,11 +83,23 @@ public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
public void initialize() {
|
||||
logger.trace("MyBMWBridgeHandler.initialize");
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
MyBMWBridgeConfiguration config = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
if (config.language.equals(Constants.LANGUAGE_AUTODETECT)) {
|
||||
config.language = localeProvider.getLocale().getLanguage().toLowerCase();
|
||||
|
||||
this.bmwBridgeConfiguration = Optional.of(getConfigAs(MyBMWBridgeConfiguration.class));
|
||||
|
||||
MyBMWBridgeConfiguration localBridgeConfiguration;
|
||||
|
||||
if (bmwBridgeConfiguration.isPresent()) {
|
||||
localBridgeConfiguration = bmwBridgeConfiguration.get();
|
||||
} else {
|
||||
logger.warn("the bridge configuration could not be retrieved");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!MyBMWConfigurationChecker.checkConfiguration(config)) {
|
||||
|
||||
if (localBridgeConfiguration.getLanguage().equals(Constants.LANGUAGE_AUTODETECT)) {
|
||||
localBridgeConfiguration.setLanguage(localeProvider.getLocale().getLanguage().toLowerCase());
|
||||
}
|
||||
if (!MyBMWConfigurationChecker.checkInitialConfiguration(localBridgeConfiguration)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
} else {
|
||||
// there is no risk in this functionality as several steps have to happen to get the file proxy working:
|
||||
@ -100,19 +113,26 @@ public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
environment = "";
|
||||
}
|
||||
|
||||
createMyBmwProxy(config, environment);
|
||||
// this access has to be synchronized as the vehicleHandler as well as the bridge itself request the
|
||||
// instance
|
||||
Optional<MyBMWProxy> localProxy = getMyBmwProxy();
|
||||
localProxy.ifPresent(proxy -> proxy.setBridgeConfiguration(localBridgeConfiguration));
|
||||
|
||||
initializerJob = Optional.of(scheduler.schedule(this::discoverVehicles, 2, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void createMyBmwProxy(MyBMWBridgeConfiguration config, String environment) {
|
||||
if (!myBmwProxy.isPresent()) {
|
||||
if (!(TEST.equals(environment) && TESTUSER.equals(config.userName))) {
|
||||
if (!(TEST.equals(environment) && TESTUSER.equals(config.getUserName()))) {
|
||||
myBmwProxy = Optional.of(new MyBMWHttpProxy(httpClientFactory, config));
|
||||
} else {
|
||||
myBmwProxy = Optional.of(new MyBMWFileProxy(httpClientFactory, config));
|
||||
}
|
||||
logger.trace("MyBMWBridgeHandler proxy set");
|
||||
} else {
|
||||
myBmwProxy.get().setBridgeConfiguration(config);
|
||||
logger.trace("MyBMWBridgeHandler update proxy with bridge configuration");
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,10 +155,6 @@ public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
private void discoverVehicles() {
|
||||
logger.trace("MyBMWBridgeHandler.requestVehicles");
|
||||
|
||||
MyBMWBridgeConfiguration config = getConfigAs(MyBMWBridgeConfiguration.class);
|
||||
|
||||
myBmwProxy.ifPresent(proxy -> proxy.setBridgeConfiguration(config));
|
||||
|
||||
vehicleDiscovery.ifPresent(discovery -> discovery.discoverVehicles());
|
||||
}
|
||||
|
||||
@ -148,9 +164,23 @@ public class MyBMWBridgeHandler extends BaseBridgeHandler {
|
||||
return List.of(VehicleDiscovery.class);
|
||||
}
|
||||
|
||||
public Optional<MyBMWProxy> getMyBmwProxy() {
|
||||
public synchronized Optional<MyBMWProxy> getMyBmwProxy() {
|
||||
logger.trace("MyBMWBridgeHandler.getProxy");
|
||||
createMyBmwProxy(getConfigAs(MyBMWBridgeConfiguration.class), ENVIRONMENT);
|
||||
|
||||
MyBMWBridgeConfiguration localBridgeConfiguration = null;
|
||||
|
||||
if (bmwBridgeConfiguration.isPresent()) {
|
||||
localBridgeConfiguration = bmwBridgeConfiguration.get();
|
||||
} else {
|
||||
logger.warn("the bridge configuration could not be retrieved");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
throw new IllegalStateException("bridge handler - configuration is not available");
|
||||
}
|
||||
|
||||
if (!myBmwProxy.isPresent()) {
|
||||
createMyBmwProxy(localBridgeConfiguration, ENVIRONMENT);
|
||||
}
|
||||
|
||||
return myBmwProxy;
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ public class RemoteServiceExecutor {
|
||||
serviceExecuting.ifPresentOrElse(service -> {
|
||||
if (counter >= GIVEUP_COUNTER) {
|
||||
logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.TIMEOUT.name().toLowerCase());
|
||||
reset();
|
||||
// immediately refresh data
|
||||
@ -107,7 +107,7 @@ public class RemoteServiceExecutor {
|
||||
|
||||
private void handleRemoteServiceException(NetworkException e) {
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(e.getStatus()));
|
||||
reset();
|
||||
}
|
||||
@ -117,12 +117,12 @@ public class RemoteServiceExecutor {
|
||||
if (!executionStatusContainer.getEventId().isEmpty()) {
|
||||
// service initiated - store event id for further MyBMW updates
|
||||
executingEventId = Optional.of(executionStatusContainer.getEventId());
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
ExecutionState.INITIATED.name().toLowerCase());
|
||||
} else if (!executionStatusContainer.getEventStatus().isEmpty()) {
|
||||
// service status updated
|
||||
synchronized (this) {
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
|
||||
handler.updateRemoteExecutionStatus(serviceExecuting.orElse(Constants.EMPTY),
|
||||
executionStatusContainer.getEventStatus().toLowerCase());
|
||||
if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
|
||||
|| ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
|
||||
|
@ -23,6 +23,7 @@ import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.EADRAX_SE
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.LOGIN_NONCE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.OAUTH_ENDPOINT;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.OCP_APIM_KEYS;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REFRESH_TOKEN;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_CHINA;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_NORTH_AMERICA;
|
||||
import static org.openhab.binding.mybmw.internal.utils.BimmerConstants.REGION_ROW;
|
||||
@ -36,6 +37,7 @@ import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE_CHALLE
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CODE_VERIFIER;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.CONTENT_TYPE_URL_ENCODED;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.GRANT_TYPE;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HCAPTCHA_TOKEN;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_ACP_SUBSCRIPTION_KEY;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_BMW_CORRELATION_ID;
|
||||
import static org.openhab.binding.mybmw.internal.utils.HTTPConstants.HEADER_X_CORRELATION_ID;
|
||||
@ -57,6 +59,8 @@ import java.security.PublicKey;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
@ -86,7 +90,8 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* requests the tokens for MyBMW API authorization
|
||||
*
|
||||
* thanks to bimmer_connected https://github.com/bimmerconnected/bimmer_connected
|
||||
* thanks to bimmer_connected
|
||||
* https://github.com/bimmerconnected/bimmer_connected
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted from myBmwProxy
|
||||
@ -97,14 +102,18 @@ public class MyBMWTokenController {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWTokenController.class);
|
||||
|
||||
private Token token = new Token();
|
||||
private MyBMWBridgeConfiguration configuration;
|
||||
private MyBMWBridgeConfiguration bridgeConfiguration;
|
||||
private HttpClient httpClient;
|
||||
|
||||
public MyBMWTokenController(MyBMWBridgeConfiguration configuration, HttpClient httpClient) {
|
||||
this.configuration = configuration;
|
||||
this.bridgeConfiguration = configuration;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets new token if old one is expired or invalid. In case of error the token
|
||||
* remains.
|
||||
@ -114,18 +123,31 @@ public class MyBMWTokenController {
|
||||
* @return token
|
||||
*/
|
||||
public Token getToken() {
|
||||
if (!token.isValid()) {
|
||||
if (!bridgeConfiguration.getHcaptchatoken().isBlank()) {
|
||||
// if the hcaptchastring is available, then a new login is triggered
|
||||
boolean tokenCreationSuccess = getInitialToken();
|
||||
|
||||
if (!tokenCreationSuccess) {
|
||||
this.token = new Token();
|
||||
logger.warn(
|
||||
"initial Authentication failed, maybe request a new captcha token, see https://bimmer-connected.readthedocs.io/en/latest/captcha/rest_of_world.html!");
|
||||
}
|
||||
|
||||
// reset the token as it times out
|
||||
bridgeConfiguration.setHcaptchatoken(Constants.EMPTY);
|
||||
} else if (!token.isValid() && !Constants.EMPTY.equals(token.getRefreshToken())) {
|
||||
// if the token is invalid, try to refresh the token
|
||||
boolean tokenUpdateSuccess = false;
|
||||
switch (configuration.region) {
|
||||
switch (bridgeConfiguration.getRegion()) {
|
||||
case REGION_CHINA:
|
||||
tokenUpdateSuccess = updateTokenChina();
|
||||
tokenUpdateSuccess = getAndUpdateTokenChina();
|
||||
break;
|
||||
case REGION_NORTH_AMERICA:
|
||||
case REGION_ROW:
|
||||
tokenUpdateSuccess = updateToken();
|
||||
tokenUpdateSuccess = getUpdatedToken();
|
||||
break;
|
||||
default:
|
||||
logger.warn("Region {} not supported", configuration.region);
|
||||
logger.warn("Region {} not supported", bridgeConfiguration.getRegion());
|
||||
break;
|
||||
}
|
||||
if (!tokenUpdateSuccess) {
|
||||
@ -143,32 +165,12 @@ public class MyBMWTokenController {
|
||||
*
|
||||
* @return true if the token was successfully updated
|
||||
*/
|
||||
private synchronized boolean updateToken() {
|
||||
private synchronized boolean getInitialToken() {
|
||||
try {
|
||||
/*
|
||||
* Step 1) Get basic values for further queries
|
||||
*/
|
||||
String uuidString = UUID.randomUUID().toString();
|
||||
|
||||
String authValuesUrl = "https://" + EADRAX_SERVER_MAP.get(configuration.region) + API_OAUTH_CONFIG;
|
||||
Request authValuesRequest = httpClient.newRequest(authValuesUrl);
|
||||
authValuesRequest.header(HEADER_ACP_SUBSCRIPTION_KEY, OCP_APIM_KEYS.get(configuration.region));
|
||||
authValuesRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
authValuesRequest.header(HEADER_X_IDENTITY_PROVIDER, AUTH_PROVIDER);
|
||||
authValuesRequest.header(HEADER_X_CORRELATION_ID, uuidString);
|
||||
authValuesRequest.header(HEADER_BMW_CORRELATION_ID, uuidString);
|
||||
|
||||
ContentResponse authValuesResponse = authValuesRequest.send();
|
||||
if (authValuesResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
|
||||
+ authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
|
||||
authValuesResponse);
|
||||
}
|
||||
AuthQueryResponse aqr = JsonStringDeserializer.deserializeString(authValuesResponse.getContentAsString(),
|
||||
AuthQueryResponse.class);
|
||||
|
||||
logger.trace("authQueryResponse: {}", aqr);
|
||||
AuthQueryResponse aqr = getBasicAuthenticationValues();
|
||||
|
||||
/*
|
||||
* Step 2) Calculate values for oauth base parameters
|
||||
@ -188,17 +190,19 @@ public class MyBMWTokenController {
|
||||
baseParams.put(CODE_CHALLENGE_METHOD, "S256");
|
||||
|
||||
/**
|
||||
* Step 3) Authorization with username and password
|
||||
* Step 3) Authentication with username and password
|
||||
*/
|
||||
String loginUrl = aqr.gcdmBaseUrl + OAUTH_ENDPOINT;
|
||||
Request loginRequest = httpClient.POST(loginUrl);
|
||||
|
||||
loginRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
loginRequest.header(HCAPTCHA_TOKEN, bridgeConfiguration.getHcaptchatoken());
|
||||
|
||||
MultiMap<@Nullable String> loginParams = new MultiMap<>(baseParams);
|
||||
loginParams.put(GRANT_TYPE, AUTHORIZATION_CODE);
|
||||
loginParams.put(USERNAME, configuration.userName);
|
||||
loginParams.put(PASSWORD, configuration.password);
|
||||
loginParams.put(USERNAME, bridgeConfiguration.getUserName());
|
||||
loginParams.put(PASSWORD, bridgeConfiguration.getPassword());
|
||||
logger.trace("loginParams {}", loginParams);
|
||||
loginRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(loginParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse loginResponse = loginRequest.send();
|
||||
@ -207,10 +211,11 @@ public class MyBMWTokenController {
|
||||
+ loginResponse.getStatus() + ", Message: " + loginResponse.getContentAsString(),
|
||||
loginResponse);
|
||||
}
|
||||
|
||||
String authCode = getAuthCode(loginResponse.getContentAsString());
|
||||
|
||||
/**
|
||||
* Step 4) Authorize with code
|
||||
* Step 4) Authenticate with code
|
||||
*/
|
||||
Request authRequest = httpClient.POST(loginUrl).followRedirects(false);
|
||||
MultiMap<@Nullable String> authParams = new MultiMap<>(baseParams);
|
||||
@ -248,9 +253,13 @@ public class MyBMWTokenController {
|
||||
}
|
||||
AuthResponse ar = JsonStringDeserializer.deserializeString(codeResponse.getContentAsString(),
|
||||
AuthResponse.class);
|
||||
|
||||
token.setType(ar.tokenType);
|
||||
token.setToken(ar.accessToken);
|
||||
token.setExpiration(ar.expiresIn);
|
||||
token.setRefreshToken(ar.refreshToken);
|
||||
token.setGcid(ar.gcid);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Authorization Exception: {}", e.getMessage());
|
||||
@ -258,6 +267,80 @@ public class MyBMWTokenController {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh the existing token
|
||||
*
|
||||
* @return true if token has successfully been refreshed
|
||||
*/
|
||||
private synchronized boolean getUpdatedToken() {
|
||||
try {
|
||||
/*
|
||||
* Step 1) Get basic values for further queries
|
||||
*/
|
||||
AuthQueryResponse aqr = getBasicAuthenticationValues();
|
||||
|
||||
/**
|
||||
* Step 2) Request token
|
||||
*/
|
||||
Request codeRequest = httpClient.POST(aqr.tokenEndpoint);
|
||||
String basicAuth = "Basic "
|
||||
+ Base64.getUrlEncoder().encodeToString((aqr.clientId + ":" + aqr.clientSecret).getBytes());
|
||||
codeRequest.header(HttpHeader.CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED);
|
||||
codeRequest.header(AUTHORIZATION, basicAuth);
|
||||
|
||||
MultiMap<@Nullable String> codeParams = new MultiMap<>();
|
||||
codeParams.put(SCOPE, String.join(Constants.SPACE, aqr.scopes));
|
||||
codeParams.put(REDIRECT_URI, aqr.returnUrl);
|
||||
codeParams.put(GRANT_TYPE, REFRESH_TOKEN);
|
||||
codeParams.put(REFRESH_TOKEN, token.getRefreshToken());
|
||||
codeRequest.content(new StringContentProvider(CONTENT_TYPE_URL_ENCODED,
|
||||
UrlEncoded.encode(codeParams, StandardCharsets.UTF_8, false), StandardCharsets.UTF_8));
|
||||
ContentResponse codeResponse = codeRequest.send();
|
||||
if (codeResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + codeRequest.getURI() + ", Error: " + codeResponse.getStatus()
|
||||
+ ", Message: " + codeResponse.getContentAsString(), codeResponse);
|
||||
}
|
||||
AuthResponse ar = JsonStringDeserializer.deserializeString(codeResponse.getContentAsString(),
|
||||
AuthResponse.class);
|
||||
|
||||
token.setToken(ar.accessToken);
|
||||
token.setExpiration(ar.expiresIn);
|
||||
token.setRefreshToken(ar.refreshToken);
|
||||
token.setGcid(ar.gcid);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Refresh Exception: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AuthQueryResponse getBasicAuthenticationValues()
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
String uuidString = UUID.randomUUID().toString();
|
||||
|
||||
String authValuesUrl = "https://" + EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion()) + API_OAUTH_CONFIG;
|
||||
Request authValuesRequest = httpClient.newRequest(authValuesUrl);
|
||||
authValuesRequest.header(HEADER_ACP_SUBSCRIPTION_KEY, OCP_APIM_KEYS.get(bridgeConfiguration.getRegion()));
|
||||
authValuesRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
|
||||
authValuesRequest.header(HEADER_X_IDENTITY_PROVIDER, AUTH_PROVIDER);
|
||||
authValuesRequest.header(HEADER_X_CORRELATION_ID, uuidString);
|
||||
authValuesRequest.header(HEADER_BMW_CORRELATION_ID, uuidString);
|
||||
|
||||
ContentResponse authValuesResponse = authValuesRequest.send();
|
||||
if (authValuesResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + authValuesRequest.getURI() + ", Error: "
|
||||
+ authValuesResponse.getStatus() + ", Message: " + authValuesResponse.getContentAsString(),
|
||||
authValuesResponse);
|
||||
}
|
||||
AuthQueryResponse aqr = JsonStringDeserializer.deserializeString(authValuesResponse.getContentAsString(),
|
||||
AuthQueryResponse.class);
|
||||
|
||||
logger.trace("authQueryResponse: {}", aqr);
|
||||
return aqr;
|
||||
}
|
||||
|
||||
private String generateState() {
|
||||
String stateBytes = StringUtils.getRandomAlphabetic(64).toLowerCase();
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(stateBytes.getBytes());
|
||||
@ -301,7 +384,7 @@ public class MyBMWTokenController {
|
||||
return codeFound.toString();
|
||||
}
|
||||
|
||||
private synchronized boolean updateTokenChina() {
|
||||
private synchronized boolean getAndUpdateTokenChina() {
|
||||
try {
|
||||
/**
|
||||
* Step 1) get public key
|
||||
@ -310,7 +393,7 @@ public class MyBMWTokenController {
|
||||
Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl);
|
||||
oauthQueryRequest.header(HttpHeader.USER_AGENT, USER_AGENT);
|
||||
oauthQueryRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
|
||||
ContentResponse publicKeyResponse = oauthQueryRequest.send();
|
||||
if (publicKeyResponse.getStatus() != 200) {
|
||||
throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: "
|
||||
@ -335,7 +418,7 @@ public class MyBMWTokenController {
|
||||
// https://www.thexcoders.net/java-ciphers-rsa/
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encryptedBytes = cipher.doFinal(configuration.password.getBytes());
|
||||
byte[] encryptedBytes = cipher.doFinal(bridgeConfiguration.getPassword().getBytes());
|
||||
String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
|
||||
/**
|
||||
@ -344,9 +427,9 @@ public class MyBMWTokenController {
|
||||
String tokenUrl = "https://" + EADRAX_SERVER_MAP.get(REGION_CHINA) + CHINA_LOGIN;
|
||||
Request loginRequest = httpClient.POST(tokenUrl);
|
||||
loginRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW,
|
||||
APP_VERSIONS.get(configuration.region), configuration.region));
|
||||
String jsonContent = "{ \"mobile\":\"" + configuration.userName + "\", \"password\":\"" + encodedPassword
|
||||
+ "\"}";
|
||||
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
|
||||
String jsonContent = "{ \"mobile\":\"" + bridgeConfiguration.getUserName() + "\", \"password\":\""
|
||||
+ encodedPassword + "\"}";
|
||||
loginRequest.content(new StringContentProvider(jsonContent));
|
||||
ContentResponse tokenResponse = loginRequest.send();
|
||||
if (tokenResponse.getStatus() != 200) {
|
||||
|
@ -19,15 +19,20 @@ import org.openhab.binding.mybmw.internal.utils.Constants;
|
||||
* The {@link Token} MyBMW Token storage
|
||||
*
|
||||
* @author Bernd Weymann - Initial contribution
|
||||
* @author Martin Grassl - extracted to own class
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Token {
|
||||
private String token = Constants.EMPTY;
|
||||
private String tokenType = Constants.EMPTY;
|
||||
private String refreshToken = Constants.EMPTY;
|
||||
private String gcid = Constants.EMPTY;
|
||||
|
||||
private long expiration = 0;
|
||||
|
||||
public String getBearerToken() {
|
||||
return new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
|
||||
return token.equals(Constants.EMPTY) ? Constants.EMPTY
|
||||
: new StringBuilder(tokenType).append(Constants.SPACE).append(token).toString();
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
@ -46,9 +51,25 @@ public class Token {
|
||||
tokenType = type;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void setRefreshToken(String refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getGcid() {
|
||||
return gcid;
|
||||
}
|
||||
|
||||
public void setGcid(String gcid) {
|
||||
this.gcid = gcid;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return (!token.equals(Constants.EMPTY) && !tokenType.equals(Constants.EMPTY)
|
||||
&& (this.expiration - System.currentTimeMillis() / 1000) > 1);
|
||||
&& !refreshToken.equals(Constants.EMPTY) && (this.expiration - System.currentTimeMillis() / 1000) > 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,7 +19,6 @@ import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
@ -60,17 +59,17 @@ public class MyBMWFileProxy implements MyBMWProxy {
|
||||
|
||||
public MyBMWFileProxy(HttpClientFactory httpClientFactory, MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - initialize");
|
||||
vehicleToBeTested = bridgeConfiguration.password;
|
||||
vehicleToBeTested = bridgeConfiguration.getPassword();
|
||||
}
|
||||
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
logger.trace("MyBMWFileProxy - update bridge");
|
||||
vehicleToBeTested = bridgeConfiguration.password;
|
||||
vehicleToBeTested = bridgeConfiguration.getPassword();
|
||||
}
|
||||
|
||||
public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
|
||||
List<@NonNull Vehicle> vehicles = new ArrayList<>();
|
||||
List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
public List<Vehicle> requestVehicles() throws NetworkException {
|
||||
List<Vehicle> vehicles = new ArrayList<>();
|
||||
List<VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
|
@ -22,7 +22,6 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
@ -67,7 +66,7 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWHttpProxy.class);
|
||||
private final HttpClient httpClient;
|
||||
private MyBMWBridgeConfiguration bridgeConfiguration;
|
||||
private final MyBMWTokenController myBMWTokenHandler;
|
||||
MyBMWTokenController myBMWTokenController;
|
||||
|
||||
/**
|
||||
* URLs taken from
|
||||
@ -82,16 +81,16 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
logger.trace("MyBMWHttpProxy - initialize");
|
||||
httpClient = httpClientFactory.getCommonHttpClient();
|
||||
|
||||
myBMWTokenHandler = new MyBMWTokenController(bridgeConfiguration, httpClient);
|
||||
myBMWTokenController = new MyBMWTokenController(bridgeConfiguration, httpClient);
|
||||
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
|
||||
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
vehicleUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.API_VEHICLES;
|
||||
|
||||
vehicleStateUrl = vehicleUrl + "/state";
|
||||
|
||||
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
remoteCommandUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ BimmerConstants.API_REMOTE_SERVICE_BASE_URL;
|
||||
remoteStatusUrl = remoteCommandUrl + "eventStatus";
|
||||
logger.trace("MyBMWHttpProxy - ready");
|
||||
@ -100,6 +99,7 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
@Override
|
||||
public void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration) {
|
||||
this.bridgeConfiguration = bridgeConfiguration;
|
||||
myBMWTokenController.setBridgeConfiguration(bridgeConfiguration);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,9 +107,9 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
*
|
||||
* @return list of vehicles
|
||||
*/
|
||||
public List<@NonNull Vehicle> requestVehicles() throws NetworkException {
|
||||
List<@NonNull Vehicle> vehicles = new ArrayList<>();
|
||||
List<@NonNull VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
public List<Vehicle> requestVehicles() throws NetworkException {
|
||||
List<Vehicle> vehicles = new ArrayList<>();
|
||||
List<VehicleBase> vehiclesBase = requestVehiclesBase();
|
||||
|
||||
for (VehicleBase vehicleBase : vehiclesBase) {
|
||||
VehicleStateContainer vehicleState = requestVehicleState(vehicleBase.getVin(),
|
||||
@ -177,7 +177,7 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
* @return the image as a byte array
|
||||
*/
|
||||
public byte[] requestImage(String vin, String brand, ImageProperties props) throws NetworkException {
|
||||
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
final String localImageUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ "/eadrax-ics/v3/presentation/vehicles/" + vin + "/images?carView=" + props.viewport;
|
||||
return get(localImageUrl, brand, vin, HTTPConstants.CONTENT_TYPE_IMAGE);
|
||||
}
|
||||
@ -231,7 +231,7 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
chargeStatisticsParams.put("vin", vin);
|
||||
chargeStatisticsParams.put("currentDate", Converter.getCurrentISOTime());
|
||||
String params = UrlEncoded.encode(chargeStatisticsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
String chargeStatisticsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ "/eadrax-chs/v1/charging-statistics?" + params;
|
||||
byte[] chargeStatisticsResponse = get(chargeStatisticsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeStatisticsResponseString = new String(chargeStatisticsResponse);
|
||||
@ -263,7 +263,7 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
chargeSessionsParams.put("maxResults", "40");
|
||||
chargeSessionsParams.put("include_date_picker", "true");
|
||||
String params = UrlEncoded.encode(chargeSessionsParams, StandardCharsets.UTF_8, false);
|
||||
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.region)
|
||||
String chargeSessionsUrl = "https://" + BimmerConstants.EADRAX_SERVER_MAP.get(bridgeConfiguration.getRegion())
|
||||
+ "/eadrax-chs/v1/charging-sessions?" + params;
|
||||
byte[] chargeSessionsResponse = get(chargeSessionsUrl, brand, vin, HTTPConstants.CONTENT_TYPE_JSON);
|
||||
String chargeSessionsResponseString = new String(chargeSessionsResponse);
|
||||
@ -353,6 +353,12 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
throw new NetworkException("Unknown Brand " + brand);
|
||||
}
|
||||
|
||||
// if no token is available, no request can be triggered
|
||||
if (Constants.EMPTY.equals(myBMWTokenController.getToken().getBearerToken())) {
|
||||
logger.warn("The login failed, no token is available");
|
||||
throw new NetworkException("The login failed, no token is available");
|
||||
}
|
||||
|
||||
final Request req;
|
||||
|
||||
if (post) {
|
||||
@ -361,10 +367,10 @@ public class MyBMWHttpProxy implements MyBMWProxy {
|
||||
req = httpClient.newRequest(url);
|
||||
}
|
||||
|
||||
req.header(HttpHeader.AUTHORIZATION, myBMWTokenHandler.getToken().getBearerToken());
|
||||
req.header(HttpHeader.AUTHORIZATION, myBMWTokenController.getToken().getBearerToken());
|
||||
req.header(HTTPConstants.HEADER_X_USER_AGENT, String.format(BimmerConstants.X_USER_AGENT, brand.toLowerCase(),
|
||||
APP_VERSIONS.get(bridgeConfiguration.region), bridgeConfiguration.region));
|
||||
req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.language);
|
||||
APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion()));
|
||||
req.header(HttpHeader.ACCEPT_LANGUAGE, bridgeConfiguration.getLanguage());
|
||||
req.header(HttpHeader.ACCEPT, contentType);
|
||||
req.header(HTTPConstants.HEADER_BMW_VIN, vin);
|
||||
|
||||
|
@ -14,7 +14,6 @@ package org.openhab.binding.mybmw.internal.handler.backend;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNull;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
@ -36,7 +35,7 @@ public interface MyBMWProxy {
|
||||
|
||||
void setBridgeConfiguration(MyBMWBridgeConfiguration bridgeConfiguration);
|
||||
|
||||
List<@NonNull Vehicle> requestVehicles() throws NetworkException;
|
||||
List<Vehicle> requestVehicles() throws NetworkException;
|
||||
|
||||
/**
|
||||
* request all vehicles for one specific brand and their state
|
||||
|
@ -59,17 +59,18 @@ public interface BimmerConstants {
|
||||
static final String CHINA_LOGIN = "/eadrax-coas/v2/login/pwd";
|
||||
|
||||
// Http variables
|
||||
static final String APP_VERSION_NORTH_AMERICA = "2.12.0(19883)";
|
||||
static final String APP_VERSION_ROW = "2.12.0(19883)";
|
||||
static final String APP_VERSION_CHINA = "2.3.0(13603)";
|
||||
static final String APP_VERSION_NORTH_AMERICA = "4.9.2(36892)";
|
||||
static final String APP_VERSION_ROW = "4.9.2(36892)";
|
||||
static final String APP_VERSION_CHINA = "4.9.2(36892)";
|
||||
static final Map<String, String> APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW,
|
||||
APP_VERSION_ROW, REGION_CHINA, APP_VERSION_CHINA);
|
||||
static final String USER_AGENT = "Dart/2.16 (dart:io)";
|
||||
// see const.py of bimmer_constants: user-agent; brand; app_version; region
|
||||
static final String X_USER_AGENT = "android(SP1A.210812.016.C1);%s;%s;%s";
|
||||
static final String X_USER_AGENT = "android(AP2A.240605.024);%s;%s;%s";
|
||||
|
||||
static final String LOGIN_NONCE = "login_nonce";
|
||||
static final String AUTHORIZATION_CODE = "authorization_code";
|
||||
static final String REFRESH_TOKEN = "refresh_token";
|
||||
|
||||
// Parameters for API Requests
|
||||
static final String TIRE_GUARD_MODE = "tireGuardMode";
|
||||
|
@ -42,6 +42,7 @@ public interface HTTPConstants {
|
||||
static final String CREDENTIALS = "Credentials";
|
||||
static final String USERNAME = "username";
|
||||
static final String PASSWORD = "password";
|
||||
static final String HCAPTCHA_TOKEN = "hcaptchatoken";
|
||||
static final String CONTENT_LENGTH = "Content-Length";
|
||||
static final String CODE_CHALLENGE = "code_challenge";
|
||||
static final String CODE_CHALLENGE_METHOD = "code_challenge_method";
|
||||
|
@ -24,11 +24,11 @@ import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class MyBMWConfigurationChecker {
|
||||
public static boolean checkConfiguration(MyBMWBridgeConfiguration config) {
|
||||
if (config.userName.isBlank() || config.password.isBlank()) {
|
||||
public static boolean checkInitialConfiguration(MyBMWBridgeConfiguration config) {
|
||||
if (config.getUserName().isBlank() || config.getPassword().isBlank() || config.getHcaptchatoken().isBlank()) {
|
||||
return false;
|
||||
} else {
|
||||
return BimmerConstants.EADRAX_SERVER_MAP.containsKey(config.region);
|
||||
return BimmerConstants.EADRAX_SERVER_MAP.containsKey(config.getRegion());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,11 @@
|
||||
<description>MyBMW Password</description>
|
||||
<context>password</context>
|
||||
</parameter>
|
||||
<parameter name="hcaptchatoken" type="text" required="true">
|
||||
<label>Captcha-Token</label>
|
||||
<description>Captcha-Token for login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)</description>
|
||||
<context>hcaptchatoken</context>
|
||||
</parameter>
|
||||
<parameter name="region" type="text" required="true">
|
||||
<label>Region</label>
|
||||
<description>Select Region in order to connect to the appropriate BMW Server</description>
|
||||
|
@ -8,6 +8,8 @@ thing-type.config.mybmw.bridge.language.description = Channel data can be return
|
||||
thing-type.config.mybmw.bridge.language.label = Language Settings
|
||||
thing-type.config.mybmw.bridge.password.description = MyBMW Password
|
||||
thing-type.config.mybmw.bridge.password.label = Password
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token for the Login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html)
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
|
||||
thing-type.config.mybmw.bridge.region.description = Select Region in order to connect to the appropriate BMW Server
|
||||
thing-type.config.mybmw.bridge.region.label = Region
|
||||
thing-type.config.mybmw.bridge.region.option.CHINA = China
|
||||
|
@ -22,6 +22,8 @@ thing-type.config.mybmw.bridge.language.label = Sprachauswahl
|
||||
thing-type.config.mybmw.bridge.language.description = Daten werden für die gewünschte Sprache angefordert (en, de, fr ...)
|
||||
thing-type.config.mybmw.bridge.password.label = Passwort
|
||||
thing-type.config.mybmw.bridge.password.description = Passwort für die MyBMW App
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token
|
||||
thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token für den Login (siehe https://bimmer-connected.readthedocs.io/en/stable/captcha.html)
|
||||
thing-type.config.mybmw.bridge.region.label = Region
|
||||
thing-type.config.mybmw.bridge.region.description = Auswahl Ihrer Region
|
||||
thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = Nordamerika
|
||||
|
@ -317,9 +317,9 @@ class AuthTest {
|
||||
HttpClientFactory mockHCF = mock(HttpClientFactory.class);
|
||||
when(mockHCF.getCommonHttpClient()).thenReturn(authHttpClient);
|
||||
MyBMWBridgeConfiguration config = new MyBMWBridgeConfiguration();
|
||||
config.region = BimmerConstants.REGION_CHINA;
|
||||
config.userName = "Hello User";
|
||||
config.password = "Hello Password";
|
||||
config.setRegion(BimmerConstants.REGION_CHINA);
|
||||
config.setUserName("Hello User");
|
||||
config.setPassword("Hello Password");
|
||||
MyBMWTokenController tokenHandler = new MyBMWTokenController(config, authHttpClient);
|
||||
Token token = tokenHandler.getToken();
|
||||
assertNotNull(token);
|
||||
|
@ -36,6 +36,8 @@ import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.auth.MyBMWTokenController;
|
||||
import org.openhab.binding.mybmw.internal.handler.auth.Token;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.util.FileReader;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
@ -196,8 +198,13 @@ public class MyBMWHttpProxyTest {
|
||||
HttpClientFactory httpClientFactoryMock = Mockito.mock(HttpClientFactory.class);
|
||||
HttpClient httpClientMock = Mockito.mock(HttpClient.class);
|
||||
Request requestMock = Mockito.mock(Request.class);
|
||||
MyBMWTokenController bmwTokenControllerMock = Mockito.mock(MyBMWTokenController.class);
|
||||
Mockito.when(httpClientMock.newRequest(Mockito.anyString())).thenReturn(requestMock);
|
||||
Mockito.when(httpClientMock.POST(Mockito.anyString())).thenReturn(requestMock);
|
||||
|
||||
Token token = Mockito.mock(Token.class);
|
||||
Mockito.when(token.getBearerToken()).thenReturn("blah");
|
||||
Mockito.when(bmwTokenControllerMock.getToken()).thenReturn(token);
|
||||
MyBMWBridgeConfiguration myBMWBridgeConfiguration = new MyBMWBridgeConfiguration();
|
||||
Mockito.when(httpClientFactoryMock.getCommonHttpClient()).thenReturn(httpClientMock);
|
||||
|
||||
@ -216,6 +223,8 @@ public class MyBMWHttpProxyTest {
|
||||
logger.error(e1.getMessage(), e1);
|
||||
}
|
||||
|
||||
return new MyBMWHttpProxy(httpClientFactoryMock, myBMWBridgeConfiguration);
|
||||
MyBMWHttpProxy proxy = new MyBMWHttpProxy(httpClientFactoryMock, myBMWBridgeConfiguration);
|
||||
proxy.myBMWTokenController = bmwTokenControllerMock;
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,327 @@
|
||||
/**
|
||||
* 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.mybmw.internal.handler.backend;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleBase;
|
||||
import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.ExecutionState;
|
||||
import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
|
||||
import org.openhab.binding.mybmw.internal.utils.BimmerConstants;
|
||||
import org.openhab.binding.mybmw.internal.utils.ImageProperties;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
|
||||
/**
|
||||
* this integration test runs only if the connected account is set via environment variables
|
||||
* CONNECTED_USER
|
||||
* CONNECTED_PASSWORD
|
||||
* HCAPTCHA_TOKEN
|
||||
*
|
||||
* if you want to execute the tests, please set the env variables and remove the disabled annotation
|
||||
*
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MyBMWProxyBackendIT {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyBMWProxyBackendIT.class);
|
||||
|
||||
public MyBMWProxy initializeProxy() {
|
||||
String connectedUser = System.getenv("CONNECTED_USER");
|
||||
String connectedPassword = System.getenv("CONNECTED_PASSWORD");
|
||||
String hCaptchaString = System.getenv("HCAPTCHA_TOKEN");
|
||||
assertNotNull(connectedUser);
|
||||
assertNotNull(connectedPassword);
|
||||
assertNotNull(hCaptchaString);
|
||||
|
||||
MyBMWBridgeConfiguration configuration = new MyBMWBridgeConfiguration();
|
||||
configuration.setLanguage("de-DE");
|
||||
configuration.setRegion(BimmerConstants.REGION_ROW);
|
||||
configuration.setUserName(connectedUser);
|
||||
configuration.setPassword(connectedPassword);
|
||||
configuration.setHcaptchatoken(hCaptchaString);
|
||||
|
||||
return new MyBMWHttpProxy(new MyHttpClientFactory(), configuration);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setupLogger() {
|
||||
Logger root = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
|
||||
|
||||
((ch.qos.logback.classic.Logger) root).setLevel(Level.TRACE);
|
||||
|
||||
logger.trace("tracing enabled");
|
||||
logger.debug("debugging enabled");
|
||||
logger.info("info enabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSequence() {
|
||||
MyBMWProxy myBMWProxy = initializeProxy();
|
||||
|
||||
// get list of vehicles
|
||||
List<VehicleBase> vehicles = null;
|
||||
try {
|
||||
vehicles = myBMWProxy.requestVehiclesBase();
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
}
|
||||
|
||||
assertNotNull(vehicles);
|
||||
assertEquals(2, vehicles.size());
|
||||
|
||||
for (VehicleBase vehicleBase : vehicles) {
|
||||
assertNotNull(vehicleBase.getVin());
|
||||
assertNotNull(vehicleBase.getAttributes().getBrand());
|
||||
|
||||
// get image
|
||||
try {
|
||||
byte[] bmwImage = myBMWProxy.requestImage(vehicleBase.getVin(), vehicleBase.getAttributes().getBrand(),
|
||||
new ImageProperties());
|
||||
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
}
|
||||
|
||||
// get state
|
||||
VehicleStateContainer vehicleState = null;
|
||||
try {
|
||||
vehicleState = myBMWProxy.requestVehicleState(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
}
|
||||
assertNotNull(vehicleState);
|
||||
|
||||
// get charge statistics -> only successful for electric vehicles
|
||||
ChargingStatisticsContainer chargeStatisticsContainer = null;
|
||||
try {
|
||||
chargeStatisticsContainer = myBMWProxy.requestChargeStatistics(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
assertNotNull(chargeStatisticsContainer);
|
||||
} catch (NetworkException e) {
|
||||
logger.trace("error: {}", e.toString());
|
||||
}
|
||||
|
||||
ChargingSessionsContainer chargeSessionsContainer = null;
|
||||
try {
|
||||
chargeSessionsContainer = myBMWProxy.requestChargeSessions(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand());
|
||||
assertNotNull(chargeSessionsContainer);
|
||||
} catch (NetworkException e) {
|
||||
logger.trace("error: {}", e.toString());
|
||||
}
|
||||
|
||||
ExecutionStatusContainer remoteExecutionResponse = null;
|
||||
try {
|
||||
remoteExecutionResponse = myBMWProxy.executeRemoteServiceCall(vehicleBase.getVin(),
|
||||
vehicleBase.getAttributes().getBrand(), RemoteService.LIGHT_FLASH);
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
}
|
||||
|
||||
assertNotNull(remoteExecutionResponse);
|
||||
logger.warn("{}", remoteExecutionResponse.toString());
|
||||
|
||||
ExecutionStatusContainer remoteExecutionStatusResponse = null;
|
||||
try {
|
||||
remoteExecutionStatusResponse = myBMWProxy.executeRemoteServiceStatusCall(
|
||||
vehicleBase.getAttributes().getBrand(), remoteExecutionResponse.getEventId());
|
||||
|
||||
assertNotNull(remoteExecutionStatusResponse);
|
||||
logger.warn("{}", remoteExecutionStatusResponse.toString());
|
||||
|
||||
int counter = 0;
|
||||
while (!ExecutionState.EXECUTED.toString().equals(remoteExecutionStatusResponse.getEventStatus())
|
||||
&& counter++ < 10) {
|
||||
remoteExecutionStatusResponse = myBMWProxy.executeRemoteServiceStatusCall(
|
||||
vehicleBase.getAttributes().getBrand(), remoteExecutionResponse.getEventId());
|
||||
logger.warn("{}", remoteExecutionStatusResponse.toString());
|
||||
|
||||
Thread.sleep(5000);
|
||||
}
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
} catch (InterruptedException e) {
|
||||
fail(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetImages() {
|
||||
MyBMWProxy myBMWProxy = initializeProxy();
|
||||
|
||||
ImageProperties imageProperties = new ImageProperties();
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "VehicleStatus";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "SideViewLeft";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "AngleSideViewForty";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "FrontView";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "FrontLeft";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "FrontRight";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
|
||||
try {
|
||||
imageProperties.viewport = "RearView";
|
||||
byte[] bmwImage = myBMWProxy.requestImage("please_set_here_your_vin", "bmw", imageProperties);
|
||||
Files.write(new File("./" + imageProperties.viewport + ".jpg").toPath(), bmwImage);
|
||||
assertNotNull(bmwImage);
|
||||
} catch (NetworkException | IOException e) {
|
||||
logger.error("error retrieving image", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testGetVehicles() {
|
||||
MyBMWProxy myBMWProxy = initializeProxy();
|
||||
|
||||
try {
|
||||
List<Vehicle> vehicles = myBMWProxy.requestVehicles();
|
||||
|
||||
logger.warn(ResponseContentAnonymizer.anonymizeResponseContent(new Gson().toJson(vehicles)));
|
||||
assertNotNull(vehicles);
|
||||
assertEquals(2, vehicles.size());
|
||||
} catch (NetworkException e) {
|
||||
fail(e.getReason(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Martin Grassl - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class MyHttpClientFactory implements HttpClientFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(MyHttpClientFactory.class);
|
||||
|
||||
@Override
|
||||
public HttpClient createHttpClient(String consumerName) {
|
||||
// Instantiate and configure the SslContextFactory
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
|
||||
// Instantiate HttpClient with the SslContextFactory
|
||||
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||
|
||||
// Configure HttpClient, for example:
|
||||
httpClient.setFollowRedirects(false);
|
||||
|
||||
// Start HttpClient
|
||||
try {
|
||||
httpClient.start();
|
||||
} catch (Exception e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getCommonHttpClient() {
|
||||
return createHttpClient("test");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HTTP2Client createHttp2Client(String arg0) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'createHttp2Client'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HTTP2Client createHttp2Client(String arg0, @Nullable SslContextFactory arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'createHttp2Client'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient createHttpClient(String arg0, @Nullable SslContextFactory arg1) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'createHttpClient'");
|
||||
}
|
||||
}
|
@ -30,19 +30,21 @@ import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration;
|
||||
public class MyBMWConfigurationCheckerTest {
|
||||
@Test
|
||||
void testCheckConfiguration() {
|
||||
MyBMWBridgeConfiguration cdc = new MyBMWBridgeConfiguration();
|
||||
assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.userName = "a";
|
||||
assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.password = "b";
|
||||
assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.region = "c";
|
||||
assertFalse(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.region = BimmerConstants.REGION_NORTH_AMERICA;
|
||||
assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.region = BimmerConstants.REGION_ROW;
|
||||
assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
cdc.region = BimmerConstants.REGION_CHINA;
|
||||
assertTrue(MyBMWConfigurationChecker.checkConfiguration(cdc));
|
||||
MyBMWBridgeConfiguration myBMWBridgeConfiguration = new MyBMWBridgeConfiguration();
|
||||
assertFalse(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setUserName("a");
|
||||
assertFalse(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setPassword("b");
|
||||
assertFalse(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setHcaptchatoken("d");
|
||||
assertFalse(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setRegion("c");
|
||||
assertFalse(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setRegion(BimmerConstants.REGION_NORTH_AMERICA);
|
||||
assertTrue(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setRegion(BimmerConstants.REGION_ROW);
|
||||
assertTrue(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
myBMWBridgeConfiguration.setRegion(BimmerConstants.REGION_CHINA);
|
||||
assertTrue(MyBMWConfigurationChecker.checkInitialConfiguration(myBMWBridgeConfiguration));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user