diff --git a/bundles/org.openhab.binding.mercedesme/README.md b/bundles/org.openhab.binding.mercedesme/README.md
index 964ac2c3d02..5096675ece2 100644
--- a/bundles/org.openhab.binding.mercedesme/README.md
+++ b/bundles/org.openhab.binding.mercedesme/README.md
@@ -7,12 +7,11 @@ This binding provides access to your Mercedes Benz vehicle like _Mercedes Me_ Sm
First time users shall follow the following sequence
1. Setup and configure [Bridge](#bridge-configuration)
-2. Follow the [Bridge Authorization](#bridge-authorization) process
-3. [Discovery](#discovery) shall find now vehicles associated to your account
-4. Add your vehicle from discovery and [configure](#thing-configuration) it with correct VIN
-5. Connect your desired items in UI or [text-configuration](#full-example)
-6. Optional: you can [Discover your Vehicle](#discover-your-vehicle) more deeply
-7. In case of problems check [Troubleshooting](#troubleshooting) section
+2. [Discovery](#discovery) shall find now vehicles associated to your account
+3. Add your vehicle from discovery and [configure](#thing-configuration) it with correct VIN
+4. Connect your desired items in UI or [text-configuration](#full-example)
+5. Optional: you can [Discover your Vehicle](#discover-your-vehicle) more deeply
+6. In case of problems check [Troubleshooting](#troubleshooting) section
## Supported Things
@@ -35,14 +34,20 @@ There's no manual discovery!
Bridge needs configuration in order to connect properly to your Mercedes Me account.
-| Name | Type | Description | Default | Required | Advanced |
-|-----------------|---------|-----------------------------------------|-------------|----------|----------|
-| email | text | Mercedes Me registered email Address | N/A | yes | no |
-| pin | text | Mercedes Me Smartphone App PIN | N/A | no | no |
-| region | text | Your region | EU | yes | no |
-| refreshInterval | integer | API refresh interval | 15 | yes | no |
-| callbackIP | text | IP Address of openHAB Device | N/A | yes | yes |
-| callbackPort | integer | Port Number of openHAB Device | N/A | yes | yes |
+| Name | Type | Description | Default | Required |
+|-------------------|---------|---------------------------------------------|---------------------------|----------|
+| email | text | Mercedes Me registered email Address | N/A | yes |
+| refreshToken | text | Refresh Token from MB Token Requester app | takeover previous token | yes |
+| pin | text | Mercedes Me Smartphone App PIN | N/A | no |
+| region | text | Your region | EU | yes |
+| refreshInterval | integer | API refresh interval | 15 | yes |
+
+`refreshToken` is needed to get access to your Mercedes Me account.
+Users already running this binding can stay on default value `takeover previous token`.
+New users need to generate `refreshToken` with [MB Token Requester app]( https://github.com/ReneNulschDE/mbapi2020/wiki/How%E2%80%90to:-create-the-access-and-refresh-token ).
+It simulates the Mercedes Me application *only for authorization process* on your computer, **not your openHAB system!**
+The generated *refresh token* has to be pasted into the bridge configuration.
+The generated *token* can be ignored!
Set `region` to your location
@@ -63,46 +68,6 @@ Commands protected by PIN
- Open / Ventilate Windows
- Open / Lift Sunroof
-IP `callbackIP` and port `callbackPort` will be auto-detected.
-If you're running on server with more than one network interface please select manually.
-
-### Bridge Authorization
-
-Authorization is needed to activate the Bridge which is connected to your Mercedes Me Account.
-The Bridge will indicate in the status headline if authorization is needed including the URL which needs to be opened in your browser.
-
-Three steps are needed
-
-1. Open the mentioned URL like 192.168.x.x:8090/mb-auth
-Opening this URL will request a PIN which will be send to your configured email.
-Check your Mail Account if you received the PIN.
-Click on _Continue_ to proceed with Step 2.
-
-2. Enter your PIN in the shown field.
-Leave GUID as identifier as it is.
-Click on _Submit_ button.
-
-3. Confirmation shall be shown that authorization was successful.
-
-In case of non successful authorization check your log for errors.
-Below screenshots are illustrating the authorization flow.
-
-### After Bridge Setup
-
-
-
-### Authorization Step 1
-
-
-
-### Authorization Step 2
-
-
-
-### Authorization Step 3
-
-
-
## Thing Configuration
| Name | Type | Description | Default | Required | Advanced |
@@ -814,7 +779,7 @@ Keep these 3 channels disconnected during normal operation.
### Things file
```java
-Bridge mercedesme:account:4711 "Mercedes Me John Doe" [ email="YOUR_MAIL_ADDRESS", region="EU", pin=9876, refreshInterval=15] {
+Bridge mercedesme:account:4711 "Mercedes Me John Doe" [ email="YOUR_MAIL_ADDRESS", region="EU", pin=9876, refreshToken="abc", refreshInterval=15] {
Thing bev eqa "Mercedes EQA" [ vin="VEHICLE_VIN", batteryCapacity=66.5]
}
```
diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png
deleted file mode 100644
index a5b6c179b3c..00000000000
Binary files a/bundles/org.openhab.binding.mercedesme/doc/OH-Step0.png and /dev/null differ
diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png
deleted file mode 100644
index 9bc764f8ff2..00000000000
Binary files a/bundles/org.openhab.binding.mercedesme/doc/OH-Step1.png and /dev/null differ
diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png
deleted file mode 100644
index 8924125cfd0..00000000000
Binary files a/bundles/org.openhab.binding.mercedesme/doc/OH-Step2.png and /dev/null differ
diff --git a/bundles/org.openhab.binding.mercedesme/doc/OH-Step3.png b/bundles/org.openhab.binding.mercedesme/doc/OH-Step3.png
deleted file mode 100644
index 1d1d9d986a7..00000000000
Binary files a/bundles/org.openhab.binding.mercedesme/doc/OH-Step3.png and /dev/null differ
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/Constants.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/Constants.java
index 575a767cc1a..148bb8c7f1e 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/Constants.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/Constants.java
@@ -268,7 +268,6 @@ public class Constants {
public static final String OH_CHANNEL_CONSTANT = "constant";
public static final String OH_CHANNEL_BONUS_RANGE = "bonus";
- public static final String CALLBACK_ENDPOINT = "/mb-auth";
// https://developer.mercedes-benz.com/content-page/api_migration_guide
public static final String IMAGE_BASE_URL = "https://api.mercedes-benz.com/vehicle_images/v2";
public static final String IMAGE_EXTERIOR_RESOURCE_URL = IMAGE_BASE_URL + "/vehicles/%s";
@@ -278,9 +277,7 @@ public class Constants {
public static final String STATUS_EMAIL_MISSING = ".status.email-missing";
public static final String STATUS_REGION_MISSING = ".status.region-missing";
public static final String STATUS_REFRESH_INVALID = ".status.refresh-invalid";
- public static final String STATUS_IP_MISSING = ".status.ip-missing";
- public static final String STATUS_PORT_MISSING = ".status.port-missing";
- public static final String STATUS_SERVER_RESTART = ".status.server-restart";
+ public static final String STATUS_REFRESH_TOKEN_MISSING = ".status.refresh-token-missing";
public static final String STATUS_BRIDGE_MISSING = ".status.bridge-missing";
public static final String SPACE = " ";
@@ -346,7 +343,6 @@ public class Constants {
public static final String MAX_SOC_KEY = "maxsoc";
public static final String AUTO_UNLOCK_KEY = "autolock";
- public static final String JUNIT_SERVER_ADDR = "http://999.999.999.999:99999/mb-auth";
public static final String JUNIT_TOKEN = "junitTestToken";
public static final String JUNIT_REFRESH_TOKEN = "junitRefreshToken";
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/MercedesMeHandlerFactory.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/MercedesMeHandlerFactory.java
index 30d98a3a087..ed0524ced00 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/MercedesMeHandlerFactory.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/MercedesMeHandlerFactory.java
@@ -31,7 +31,6 @@ import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.items.MetadataRegistry;
-import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
@@ -65,7 +64,6 @@ public class MercedesMeHandlerFactory extends BaseThingHandlerFactory {
private final MercedesMeDiscoveryService discoveryService;
private final MercedesMeCommandOptionProvider mmcop;
private final MercedesMeStateOptionProvider mmsop;
- private final NetworkAddressService networkService;
private @Nullable ServiceRegistration> discoveryServiceReg;
private @Nullable MercedesMeMetadataAdjuster mdAdjuster;
@@ -76,10 +74,8 @@ public class MercedesMeHandlerFactory extends BaseThingHandlerFactory {
final @Reference LocaleProvider lp, final @Reference LocationProvider locationP,
final @Reference TimeZoneProvider tzp, final @Reference MercedesMeCommandOptionProvider cop,
final @Reference MercedesMeStateOptionProvider sop, final @Reference UnitProvider up,
- final @Reference MetadataRegistry mdr, final @Reference ItemChannelLinkRegistry iclr,
- final @Reference NetworkAddressService nas) {
+ final @Reference MetadataRegistry mdr, final @Reference ItemChannelLinkRegistry iclr) {
this.storageService = storageService;
- networkService = nas;
localeProvider = lp;
locationProvider = locationP;
mmcop = cop;
@@ -112,8 +108,7 @@ public class MercedesMeHandlerFactory extends BaseThingHandlerFactory {
discoveryServiceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService,
null);
}
- return new AccountHandler((Bridge) thing, discoveryService, httpClient, localeProvider, storageService,
- networkService);
+ return new AccountHandler((Bridge) thing, discoveryService, httpClient, localeProvider, storageService);
} else if (THING_TYPE_BEV.equals(thingTypeUID) || THING_TYPE_COMB.equals(thingTypeUID)
|| THING_TYPE_HYBRID.equals(thingTypeUID)) {
return new VehicleHandler(thing, locationProvider, mmcop, mmsop);
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java
index f6aee49314a..71f5b65a8dc 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/config/AccountConfiguration.java
@@ -26,9 +26,7 @@ public class AccountConfiguration {
public String email = NOT_SET;
public String region = NOT_SET;
+ public String refreshToken = "takeover previous token";
public String pin = NOT_SET;
public int refreshInterval = 15;
-
- public String callbackIP = NOT_SET;
- public int callbackPort = -1;
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java
index 385c20dde7e..32bc9a8fa2b 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/dto/TokenResponse.java
@@ -33,6 +33,6 @@ public class TokenResponse {
@SerializedName("token_type")
public String tokenType = Constants.NOT_SET;
@SerializedName("expires_in")
- public int expiresIn;
- public String createdOn = Instant.now().toString();
+ public int expiresIn = 0;
+ public String createdOn = Instant.MIN.toString();
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java
index 8e7e0f12312..51f7df0f85a 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/AccountHandler.java
@@ -38,15 +38,12 @@ import org.json.JSONObject;
import org.openhab.binding.mercedesme.internal.Constants;
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
import org.openhab.binding.mercedesme.internal.discovery.MercedesMeDiscoveryService;
-import org.openhab.binding.mercedesme.internal.server.AuthServer;
import org.openhab.binding.mercedesme.internal.server.AuthService;
import org.openhab.binding.mercedesme.internal.server.MBWebsocket;
import org.openhab.binding.mercedesme.internal.utils.Utils;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
-import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.Bridge;
@@ -80,7 +77,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
private static final String COMMAND_APPENDIX = "-commands";
private final Logger logger = LoggerFactory.getLogger(AccountHandler.class);
- private final NetworkAddressService networkService;
private final MercedesMeDiscoveryService discoveryService;
private final HttpClient httpClient;
private final LocaleProvider localeProvider;
@@ -89,8 +85,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
private final Map vepUpdateMap = new HashMap<>();
private final Map> capabilitiesMap = new HashMap<>();
- private Optional server = Optional.empty();
- private Optional authService = Optional.empty();
private Optional> refreshScheduler = Optional.empty();
private List eventQueue = new ArrayList<>();
private boolean updateRunning = false;
@@ -99,16 +93,16 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
private String commandCapabilitiesEndpoint = "/v1/vehicle/%s/capabilities/commands";
private String poiEndpoint = "/v1/vehicle/%s/route";
+ Optional authService = Optional.empty();
final MBWebsocket ws;
- Optional config = Optional.empty();
+ AccountConfiguration config = new AccountConfiguration();
@Nullable
ClientMessage message;
public AccountHandler(Bridge bridge, MercedesMeDiscoveryService mmds, HttpClient hc, LocaleProvider lp,
- StorageService store, NetworkAddressService nas) {
+ StorageService store) {
super(bridge);
discoveryService = mmds;
- networkService = nas;
ws = new MBWebsocket(this);
httpClient = hc;
localeProvider = lp;
@@ -122,83 +116,39 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
- config = Optional.of(getConfigAs(AccountConfiguration.class));
- autodetectCallback();
+ config = getConfigAs(AccountConfiguration.class);
String configValidReason = configValid();
if (!configValidReason.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configValidReason);
} else {
- String callbackUrl = Utils.getCallbackAddress(config.get().callbackIP, config.get().callbackPort);
- thing.setProperty("callbackUrl", callbackUrl);
- server = Optional.of(new AuthServer(httpClient, config.get(), callbackUrl));
- authService = Optional
- .of(new AuthService(this, httpClient, config.get(), localeProvider.getLocale(), storage));
- if (!server.get().start()) {
- String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
- + Constants.STATUS_SERVER_RESTART;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
- textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]");
- } else {
- refreshScheduler = Optional.of(scheduler.scheduleWithFixedDelay(this::refresh, 0,
- config.get().refreshInterval, TimeUnit.MINUTES));
- }
+ authService = Optional.of(new AuthService(this, httpClient, config, localeProvider.getLocale(), storage,
+ config.refreshToken));
+ refreshScheduler = Optional
+ .of(scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.MINUTES));
}
}
public void refresh() {
- if (server.isPresent()) {
- if (!Constants.NOT_SET.equals(authService.get().getToken())) {
- ws.run();
- } else {
- // all failed - start manual authorization
- String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
- + Constants.STATUS_AUTH_NEEDED;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]");
- }
+ if (!Constants.NOT_SET.equals(authService.get().getToken())) {
+ ws.run();
} else {
- // server not running - fix first
+ // all failed - start manual authorization
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
- + Constants.STATUS_SERVER_RESTART;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, textKey);
+ + Constants.STATUS_AUTH_NEEDED;
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, textKey);
}
}
- private void autodetectCallback() {
- // if Callback IP and Callback Port are not set => autodetect these values
- config = Optional.of(getConfigAs(AccountConfiguration.class));
- Configuration updateConfig = super.editConfiguration();
- if (!updateConfig.containsKey("callbackPort")) {
- updateConfig.put("callbackPort", Utils.getFreePort());
- } else {
- Utils.addPort(config.get().callbackPort);
- }
- if (!updateConfig.containsKey("callbackIP")) {
- String ip = networkService.getPrimaryIpv4HostAddress();
- if (ip != null) {
- updateConfig.put("callbackIP", ip);
- } else {
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
- "@text/mercedesme.account.status.ip-autodetect-failure");
- }
- }
- super.updateConfiguration(updateConfig);
- // get new config after update
- config = Optional.of(getConfigAs(AccountConfiguration.class));
- }
-
private String configValid() {
- config = Optional.of(getConfigAs(AccountConfiguration.class));
+ config = getConfigAs(AccountConfiguration.class);
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId();
- if (Constants.NOT_SET.equals(config.get().callbackIP)) {
- return textKey + Constants.STATUS_IP_MISSING;
- } else if (config.get().callbackPort == -1) {
- return textKey + Constants.STATUS_PORT_MISSING;
- } else if (Constants.NOT_SET.equals(config.get().email)) {
+ if (Constants.NOT_SET.equals(config.refreshToken)) {
+ return textKey + Constants.STATUS_REFRESH_TOKEN_MISSING;
+ } else if (Constants.NOT_SET.equals(config.email)) {
return textKey + Constants.STATUS_EMAIL_MISSING;
- } else if (Constants.NOT_SET.equals(config.get().region)) {
+ } else if (Constants.NOT_SET.equals(config.region)) {
return textKey + Constants.STATUS_REGION_MISSING;
- } else if (config.get().refreshInterval <= 01) {
+ } else if (config.refreshInterval < 5) {
return textKey + Constants.STATUS_REFRESH_INVALID;
} else {
return Constants.EMPTY;
@@ -207,13 +157,6 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
@Override
public void dispose() {
- if (server.isPresent()) {
- AuthServer authServer = server.get();
- authServer.stop();
- authServer.dispose();
- server = Optional.empty();
- Utils.removePort(config.get().callbackPort);
- }
refreshScheduler.ifPresent(schedule -> {
if (!schedule.isCancelled()) {
schedule.cancel(true);
@@ -223,6 +166,13 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
eventQueue.clear();
}
+ @Override
+ public void handleRemoval() {
+ storage.remove(config.email);
+ authService = Optional.empty();
+ super.handleRemoval();
+ }
+
/**
* https://next.openhab.org/javadoc/latest/org/openhab/core/auth/client/oauth2/package-summary.html
*/
@@ -230,27 +180,16 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
public void onAccessTokenResponse(AccessTokenResponse tokenResponse) {
if (!Constants.NOT_SET.equals(tokenResponse.getAccessToken())) {
scheduler.schedule(this::refresh, 2, TimeUnit.SECONDS);
- } else if (server.isEmpty()) {
- // server not running - fix first
- String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
- + Constants.STATUS_SERVER_RESTART;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, textKey);
} else {
// all failed - start manual authorization
String textKey = Constants.STATUS_TEXT_PREFIX + thing.getThingTypeUID().getId()
+ Constants.STATUS_AUTH_NEEDED;
- updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
- textKey + " [\"" + thing.getProperties().get("callbackUrl") + "\"]");
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, textKey);
}
}
- @Override
- public String toString() {
- return Integer.toString(config.get().callbackPort);
- }
-
public String getWSUri() {
- return Utils.getWebsocketServer(config.get().region);
+ return Utils.getWebsocketServer(config.region);
}
public ClientUpgradeRequest getClientUpgradeRequest() {
@@ -260,12 +199,12 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
request.setHeader("X-TrackingId", UUID.randomUUID().toString());
request.setHeader("Ris-Os-Name", Constants.RIS_OS_NAME);
request.setHeader("Ris-Os-Version", Constants.RIS_OS_VERSION);
- request.setHeader("Ris-Sdk-Version", Utils.getRisSDKVersion(config.get().region));
+ request.setHeader("Ris-Sdk-Version", Utils.getRisSDKVersion(config.region));
request.setHeader("X-Locale",
localeProvider.getLocale().getLanguage() + "-" + localeProvider.getLocale().getCountry()); // de-DE
- request.setHeader("User-Agent", Utils.getApplication(config.get().region));
- request.setHeader("X-Applicationname", Utils.getUserAgent(config.get().region));
- request.setHeader("Ris-Application-Version", Utils.getRisApplicationVersion(config.get().region));
+ request.setHeader("User-Agent", Utils.getApplication(config.region));
+ request.setHeader("X-Applicationname", Utils.getUserAgent(config.region));
+ request.setHeader("Ris-Application-Version", Utils.getRisApplicationVersion(config.region));
return request;
}
@@ -452,8 +391,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
Map featureMap = new HashMap<>();
try {
// add vehicle capabilities
- String capabilitiesUrl = Utils.getRestAPIServer(config.get().region)
- + String.format(capabilitiesEndpoint, vin);
+ String capabilitiesUrl = Utils.getRestAPIServer(config.region) + String.format(capabilitiesEndpoint, vin);
Request capabilitiesRequest = httpClient.newRequest(capabilitiesUrl);
authService.get().addBasicHeaders(capabilitiesRequest);
capabilitiesRequest.header("X-SessionId", UUID.randomUUID().toString());
@@ -489,7 +427,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
}
// add command capabilities
- String commandCapabilitiesUrl = Utils.getRestAPIServer(config.get().region)
+ String commandCapabilitiesUrl = Utils.getRestAPIServer(config.region)
+ String.format(commandCapabilitiesEndpoint, vin);
Request commandCapabilitiesRequest = httpClient.newRequest(commandCapabilitiesUrl);
authService.get().addBasicHeaders(commandCapabilitiesRequest);
@@ -557,7 +495,7 @@ public class AccountHandler extends BaseBridgeHandler implements AccessTokenRefr
*/
public void sendPoi(String vin, JSONObject poi) {
- String poiUrl = Utils.getRestAPIServer(config.get().region) + String.format(poiEndpoint, vin);
+ String poiUrl = Utils.getRestAPIServer(config.region) + String.format(poiEndpoint, vin);
Request poiRequest = httpClient.POST(poiUrl);
authService.get().addBasicHeaders(poiRequest);
poiRequest.header("X-SessionId", UUID.randomUUID().toString());
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java
index 586d2600f4c..12e3a533987 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandler.java
@@ -216,7 +216,7 @@ public class VehicleHandler extends BaseThingHandler {
var crBuilder = CommandRequest.newBuilder().setVin(config.get().vin).setRequestId(UUID.randomUUID().toString());
String group = channelUID.getGroupId();
String channel = channelUID.getIdWithoutGroup();
- String pin = accountHandler.get().config.get().pin;
+ String pin = accountHandler.get().config.pin;
if (group == null) {
logger.trace("No command {} found for {}", command, channel);
return;
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java
deleted file mode 100644
index 5cac0ee07d9..00000000000
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServer.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (c) 2010-2025 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.mercedesme.internal.server;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.openhab.binding.mercedesme.internal.Constants;
-import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
-import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link AuthServer} provides HTTP Server to show servlet content of the authentication process
- *
- * @author Bernd Weymann - Initial contribution
- */
-@NonNullByDefault
-public class AuthServer {
- private static final Logger LOGGER = LoggerFactory.getLogger(AuthServer.class);
- private static final Map SERVER_MAP = new HashMap<>();
- private static final AccessTokenResponse INVALID_ACCESS_TOKEN = new AccessTokenResponse();
-
- private final HttpClient httpClient;
-
- private Optional server = Optional.empty();
- private AccountConfiguration config;
- public String callbackUrl;
-
- public AuthServer(HttpClient hc, AccountConfiguration config, String callbackUrl) {
- httpClient = hc;
- SERVER_MAP.put(Integer.valueOf(config.callbackPort), this);
- this.config = config;
- this.callbackUrl = callbackUrl;
- INVALID_ACCESS_TOKEN.setAccessToken(Constants.EMPTY);
- }
-
- public void dispose() {
- SERVER_MAP.remove(Integer.valueOf(config.callbackPort));
- }
-
- public boolean start() {
- // avoid real server start for unit tests
- if (server.isPresent() || Constants.JUNIT_SERVER_ADDR.equals(callbackUrl)) {
- return true;
- }
- server = Optional.of(new Server());
- ServerConnector connector = new ServerConnector(server.get());
- connector.setPort(config.callbackPort);
- server.get().setConnectors(new Connector[] { connector });
- ServletHandler servletHandler = new ServletHandler();
- server.get().setHandler(servletHandler);
- servletHandler.addServletWithMapping(AuthServlet.class, Constants.CALLBACK_ENDPOINT);
- try {
- server.get().start();
- return true;
- } catch (Exception e) {
- LOGGER.trace("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage());
- server = Optional.empty();
- return false;
- }
- }
-
- public void stop() {
- try {
- if (server.isPresent()) {
- server.get().stop();
- server = Optional.empty();
- }
- } catch (Exception e) {
- LOGGER.trace("Cannot start Callback Server for port {}, Error {}", config.callbackPort, e.getMessage());
- }
- }
-
- @Nullable
- public static AuthServer getServer(int port) {
- return SERVER_MAP.get(port);
- }
-
- public HttpClient getHttpClient() {
- return httpClient;
- }
-
- public String getRegion() {
- return config.region;
- }
-}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java
index c660545c958..585f9a1cc35 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthService.java
@@ -16,9 +16,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
-import java.util.HashMap;
import java.util.Locale;
-import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -33,7 +31,6 @@ import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.openhab.binding.mercedesme.internal.Constants;
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
-import org.openhab.binding.mercedesme.internal.dto.PINRequest;
import org.openhab.binding.mercedesme.internal.dto.TokenResponse;
import org.openhab.binding.mercedesme.internal.utils.Utils;
import org.openhab.core.auth.client.oauth2.AccessTokenRefreshListener;
@@ -42,6 +39,8 @@ import org.openhab.core.storage.Storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.gson.JsonSyntaxException;
+
/**
* {@link AuthService} helpers for token management
*
@@ -49,23 +48,19 @@ import org.slf4j.LoggerFactory;
*/
@NonNullByDefault
public class AuthService {
- public static final AccessTokenResponse INVALID_TOKEN = new AccessTokenResponse();
private static final int EXPIRATION_BUFFER = 5;
- private static final Map AUTH_MAP = new HashMap<>();
private final Logger logger = LoggerFactory.getLogger(AuthService.class);
- AccessTokenRefreshListener listener;
+ private AccessTokenRefreshListener listener;
+ private AccountConfiguration config;
+ private AccessTokenResponse token = Utils.INVALID_TOKEN;
+ private Storage storage;
private HttpClient httpClient;
private String identifier;
- private AccountConfiguration config;
private Locale locale;
- private Storage storage;
- private AccessTokenResponse token;
public AuthService(AccessTokenRefreshListener atrl, HttpClient hc, AccountConfiguration ac, Locale l,
- Storage store) {
- INVALID_TOKEN.setAccessToken(Constants.NOT_SET);
- INVALID_TOKEN.setRefreshToken(Constants.NOT_SET);
+ Storage store, String refreshToken) {
listener = atrl;
httpClient = hc;
config = ac;
@@ -73,129 +68,53 @@ public class AuthService {
locale = l;
storage = store;
- // restore token
- String storedObject = storage.get(identifier);
- if (storedObject == null) {
- token = INVALID_TOKEN;
- listener.onAccessTokenResponse(token);
- } else {
- token = Utils.fromString(storedObject);
- if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) {
- if (!Constants.NOT_SET.equals(token.getRefreshToken())) {
- refreshToken();
- listener.onAccessTokenResponse(token);
- } else {
- token = INVALID_TOKEN;
- listener.onAccessTokenResponse(token);
+ // restore token from persistence if available
+ String storedToken = storage.get(identifier);
+ if (storedToken != null) {
+ // returns INVALID_TOKEN in case of an error
+ logger.trace("MB-Auth {} Restore token from persistence", prefix());
+ try {
+ logger.trace("MB-Auth {} storedToken {}", prefix(), storedToken);
+ TokenResponse tokenResponseJson = Utils.GSON.fromJson(storedToken, TokenResponse.class);
+ token = decodeToken(tokenResponseJson);
+ if (!tokenIsValid()) {
+ token = Utils.INVALID_TOKEN;
+ storage.remove(identifier);
+ logger.trace("MB-Auth {} invalid storedToken {}", prefix(), storedToken);
}
- } else {
- listener.onAccessTokenResponse(token);
+ } catch (JsonSyntaxException jse) {
+ // fallback of non human readable base64 token persistence
+ logger.debug("MB-Auth {} Fallback token decoding", prefix());
+ token = Utils.fromString(storedToken);
}
+ } else {
+ // initialize token with refresh token from configuration and expiration 0
+ // this will trigger an immediately refresh of the token
+ logger.trace("MB-Auth {} Create token from config", prefix());
+ token = new AccessTokenResponse();
+ token.setAccessToken(refreshToken);
+ token.setRefreshToken(refreshToken);
+ token.setExpiresIn(0);
}
- AUTH_MAP.put(config.callbackPort, this);
}
- @Nullable
- public static AuthService getAuthService(Integer key) {
- return AUTH_MAP.get(key);
+ public synchronized String getToken() {
+ if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) {
+ if (tokenIsValid()) {
+ refreshToken();
+ }
+ }
+ return token.getAccessToken();
}
- /**
- *
- * @return guid from request to create token in next step
- */
- public String requestPin() {
- String configUrl = Utils.getAuthConfigURL(config.region);
- String sessionId = UUID.randomUUID().toString();
- Request configRequest = httpClient.newRequest(configUrl);
- addBasicHeaders(configRequest);
- configRequest.header("X-Trackingid", UUID.randomUUID().toString());
- configRequest.header("X-Sessionid", sessionId);
- try {
- ContentResponse cr = configRequest.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send();
- if (cr.getStatus() == 200) {
- logger.trace("{} Config Request PIN fine {} {}", prefix(), cr.getStatus(), cr.getContentAsString());
- } else {
- logger.trace("{} Failed to request config for pin {} {}", prefix(), cr.getStatus(),
- cr.getContentAsString());
- return Constants.NOT_SET;
- }
- } catch (InterruptedException | TimeoutException | ExecutionException e) {
- logger.trace("{} Failed to request config for pin {}", prefix(), e.getMessage());
- return Constants.NOT_SET;
- }
-
- String url = Utils.getAuthURL(config.region);
- Request req = httpClient.POST(url);
- addBasicHeaders(req);
- req.header("X-Trackingid", UUID.randomUUID().toString());
- req.header("X-Sessionid", sessionId);
-
- PINRequest pr = new PINRequest(config.email, locale.getCountry());
- req.header(HttpHeader.CONTENT_TYPE, "application/json");
- logger.trace("{} payload {}", url, Utils.GSON.toJson(pr));
- req.content(new StringContentProvider(Utils.GSON.toJson(pr), "utf-8"));
-
- try {
- ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send();
- if (cr.getStatus() == 200) {
- logger.trace("{} Request PIN fine {} {}", prefix(), cr.getStatus(), cr.getContentAsString());
- return pr.nonce;
- } else {
- logger.trace("{} Failed to request pin {} {}", prefix(), cr.getStatus(), cr.getContentAsString());
- }
- } catch (InterruptedException | TimeoutException | ExecutionException e) {
- logger.trace("{} Failed to request pin {}", prefix(), e.getMessage());
- }
- return Constants.NOT_SET;
- }
-
- public boolean requestToken(String password) {
- try {
- // Request + headers
- String url = Utils.getTokenUrl(config.region);
- Request req = httpClient.POST(url);
- addBasicHeaders(req);
- req.header("Stage", "prod");
- req.header("X-Device-Id", UUID.randomUUID().toString());
- req.header("X-Request-Id", UUID.randomUUID().toString());
-
- // Content URL form
- String clientId = "client_id="
- + URLEncoder.encode(Utils.getLoginAppId(config.region), StandardCharsets.UTF_8.toString());
- String grantAttribute = "grant_type=password";
- String userAttribute = "username=" + URLEncoder.encode(config.email, StandardCharsets.UTF_8.toString());
- String passwordAttribute = "password=" + URLEncoder.encode(password, StandardCharsets.UTF_8.toString());
- String scopeAttribute = "scope=" + URLEncoder.encode(Constants.SCOPE, StandardCharsets.UTF_8.toString());
- String content = clientId + "&" + grantAttribute + "&" + userAttribute + "&" + passwordAttribute + "&"
- + scopeAttribute;
- req.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded");
- req.content(new StringContentProvider(content));
-
- // Send
- ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send();
- if (cr.getStatus() == 200) {
- String responseString = cr.getContentAsString();
- saveTokenResponse(responseString);
- listener.onAccessTokenResponse(token);
- return true;
- } else {
- logger.trace("{} Failed to get token {} {}", prefix(), cr.getStatus(), cr.getContentAsString());
- }
- } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException e) {
- logger.trace("{} Failed to get token {}", prefix(), e.getMessage());
- }
- return false;
- }
-
- public void refreshToken() {
+ private void refreshToken() {
+ logger.trace("MB-Auth {} refreshToken", prefix());
try {
String url = Utils.getTokenUrl(config.region);
Request req = httpClient.POST(url);
req.header("X-Device-Id", UUID.randomUUID().toString());
req.header("X-Request-Id", UUID.randomUUID().toString());
- // Content URL form
String grantAttribute = "grant_type=refresh_token";
String refreshTokenAttribute = "refresh_token="
+ URLEncoder.encode(token.getRefreshToken(), StandardCharsets.UTF_8.toString());
@@ -203,35 +122,79 @@ public class AuthService {
req.header(HttpHeader.CONTENT_TYPE, "application/x-www-form-urlencoded");
req.content(new StringContentProvider(content));
- // Send
ContentResponse cr = req.timeout(Constants.REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS).send();
- if (cr.getStatus() == 200) {
- saveTokenResponse(cr.getContentAsString());
- listener.onAccessTokenResponse(token);
+ int tokenResponseStatus = cr.getStatus();
+ String tokenResponse = cr.getContentAsString();
+ if (tokenResponseStatus == 200) {
+ TokenResponse tokenResponseJson = Utils.GSON.fromJson(tokenResponse, TokenResponse.class);
+ if (tokenResponseJson != null) {
+ // response doesn't contain creation date time so set it manually
+ tokenResponseJson.createdOn = Instant.now().toString();
+ // a new refresh token is delivered optional
+ // if not set in response take old one
+ if (Constants.NOT_SET.equals(tokenResponseJson.refreshToken)) {
+ tokenResponseJson.refreshToken = token.getRefreshToken();
+ }
+ token = decodeToken(tokenResponseJson);
+ if (tokenIsValid()) {
+ String tokenStore = Utils.GSON.toJson(tokenResponseJson);
+ logger.debug("MB-Auth {} refreshToken result {}", prefix(), token.toString());
+ storage.put(identifier, tokenStore);
+ } else {
+ token = Utils.INVALID_TOKEN;
+ storage.remove(identifier);
+ logger.warn("MB-Auth {} Refresh token delivered invalid result {} {}", prefix(),
+ tokenResponseStatus, tokenResponse);
+ }
+ } else {
+ logger.debug("MB-Auth {} token refersh delivered not parsable result {}", prefix(), tokenResponse);
+ token = Utils.INVALID_TOKEN;
+ }
} else {
- logger.trace("{} Failed to refresh token {} {}", prefix(), cr.getStatus(), cr.getContentAsString());
+ token = Utils.INVALID_TOKEN;
+ /**
+ * 1) remove token from storage
+ * 2) listener will be informed about INVALID_TOKEN and bridge will go OFFLINE
+ * 3) user needs to update refreshToken configuration parameter
+ */
+ storage.remove(identifier);
+ logger.warn("MB-Auth {} Failed to refresh token {} {}", prefix(), tokenResponseStatus, tokenResponse);
}
- } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException e) {
- logger.trace("{} Failed to refresh token {}", prefix(), e.getMessage());
+ listener.onAccessTokenResponse(token);
+ } catch (InterruptedException | TimeoutException | ExecutionException | UnsupportedEncodingException
+ | JsonSyntaxException e) {
+ logger.info("{} Failed to refresh token {}", prefix(), e.getMessage());
}
}
- public String getToken() {
- if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) {
- if (!Constants.NOT_SET.equals(token.getRefreshToken())) {
- refreshToken();
- // token shall be updated now - retry expired check
- if (token.isExpired(Instant.now(), EXPIRATION_BUFFER)) {
- token = INVALID_TOKEN;
- listener.onAccessTokenResponse(token);
- return Constants.NOT_SET;
- }
+ private AccessTokenResponse decodeToken(@Nullable TokenResponse tokenJson) {
+ if (tokenJson != null) {
+ AccessTokenResponse atr = new AccessTokenResponse();
+ atr.setCreatedOn(Instant.parse(tokenJson.createdOn));
+ atr.setExpiresIn(tokenJson.expiresIn);
+ atr.setAccessToken(tokenJson.accessToken);
+ if (!Constants.NOT_SET.equals(tokenJson.refreshToken)) {
+ atr.setRefreshToken(tokenJson.refreshToken);
} else {
- token = INVALID_TOKEN;
- logger.trace("{} Refresh token empty", prefix());
+ // Preserve refresh token if available
+ if (!Constants.NOT_SET.equals(token.getRefreshToken())) {
+ atr.setRefreshToken(token.getRefreshToken());
+ } else {
+ logger.debug("MB-Auth {} Neither new nor old refresh token available", prefix());
+ return Utils.INVALID_TOKEN;
+ }
}
+ atr.setTokenType("Bearer");
+ atr.setScope(Constants.SCOPE);
+ return atr;
+ } else {
+ logger.debug("MB-Auth {} Neither Token Response is null", prefix());
}
- return token.getAccessToken();
+ return Utils.INVALID_TOKEN;
+ }
+
+ private boolean tokenIsValid() {
+ return !Constants.NOT_SET.equals(token.getAccessToken()) && !Constants.NOT_SET.equals(token.getRefreshToken());
}
public void addBasicHeaders(Request req) {
@@ -244,30 +207,6 @@ public class AuthService {
req.header("Ris-Application-Version", Utils.getRisApplicationVersion(config.region));
}
- private void saveTokenResponse(String response) {
- TokenResponse tr = Utils.GSON.fromJson(response, TokenResponse.class);
- AccessTokenResponse atr = new AccessTokenResponse();
- if (tr != null) {
- atr.setAccessToken(tr.accessToken);
- atr.setCreatedOn(Instant.now());
- atr.setExpiresIn(tr.expiresIn);
- // Preserve refresh token if available
- if (Constants.NOT_SET.equals(tr.refreshToken) && !Constants.NOT_SET.equals(token.getRefreshToken())) {
- atr.setRefreshToken(token.getRefreshToken());
- } else if (!Constants.NOT_SET.equals(tr.refreshToken)) {
- atr.setRefreshToken(tr.refreshToken);
- } else {
- logger.trace("{} Neither new nor old refresh token available", prefix());
- }
- atr.setTokenType("Bearer");
- atr.setScope(Constants.SCOPE);
- storage.put(identifier, Utils.toString(atr));
- token = atr;
- } else {
- logger.trace("{} Token Response is null", prefix());
- }
- }
-
private String prefix() {
return "[" + config.email + "] ";
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java
deleted file mode 100644
index 95bfb4ec147..00000000000
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/AuthServlet.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2010-2025 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.mercedesme.internal.server;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.mercedesme.internal.Constants;
-
-/**
- * {@link AuthServlet} provides simple HTML pages for authorization workflow
- *
- * @author Bernd Weymann - Initial contribution
- */
-@SuppressWarnings("serial")
-@NonNullByDefault
-public class AuthServlet extends HttpServlet {
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response)
- throws ServletException, IOException {
- AuthService myAuthService = AuthService.getAuthService(request.getLocalPort());
- String guid = request.getParameter(Constants.GUID);
- String pin = request.getParameter(Constants.PIN);
- if (guid == null && pin == null && myAuthService != null) {
- // request PIN
- String requestVal = myAuthService.requestPin();
- if (!Constants.NOT_SET.equals(requestVal)) {
- response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().println("");
- response.getWriter().println("");
- response.getWriter().println("
Step 1 - PIN Requested
");
- response.getWriter().println(" ");
- response.getWriter().println("PIN was requested and should be present in your EMail Inbox ");
- response.getWriter()
- .println("Check first if you received the PIN and then continue with the below Link ");
- response.getWriter().println("Click here to continue with Step 2");
- response.getWriter().println("");
- response.getWriter().println("");
- } else {
- response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().println("");
- response.getWriter().println("");
- response.getWriter().println("Something went wrong ");
- response.getWriter().println("");
- response.getWriter().println("");
- }
-
- } else if (guid != null && pin == null && myAuthService != null) {
- // show insert PIN input field
-
- response.setContentType("text/html");
- response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().println("");
- response.getWriter().println("");
- response.getWriter().println("
Step 2 - Enter PIN
");
- response.getWriter().println(" ");
- response.getWriter().println("Enter PIN in second input field - leave guid as it is! ");
- response.getWriter().println("");
- response.getWriter().println("");
- response.getWriter().println("");
- } else if (guid != null && pin != null && myAuthService != null) {
- // call getToken and show result
- boolean result = myAuthService.requestToken(guid + ":" + pin);
- response.setContentType("text/html");
- response.setStatus(HttpServletResponse.SC_OK);
- response.getWriter().println("");
- response.getWriter().println("");
- response.getWriter().println("
Step 3 - Save Token
");
- response.getWriter().println(" ");
- if (result) {
- response.getWriter().println("Success - everything done! ");
- } else {
- response.getWriter().println("Failure - Please check logs for further analysis! ");
- }
- response.getWriter().println("");
- response.getWriter().println("");
- }
- }
-}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java
index 26c7ed05a93..04db1b4e034 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/server/MBWebsocket.java
@@ -93,11 +93,11 @@ public class MBWebsocket {
client.setStopTimeout(CONNECT_TIMEOUT_MS);
ClientUpgradeRequest request = accountHandler.getClientUpgradeRequest();
String websocketURL = accountHandler.getWSUri();
- logger.trace("Websocket start {}", websocketURL);
if (Constants.JUNIT_TOKEN.equals(request.getHeader("Authorization"))) {
// avoid unit test requesting real websocket - simply return
return;
}
+ logger.trace("Websocket start {}", websocketURL);
client.start();
client.connect(this, new URI(websocketURL), request);
while (keepAlive || Instant.now().isBefore(runTill)) {
@@ -177,7 +177,8 @@ public class MBWebsocket {
}
} else {
if (!b) {
- // after keep alive is finished add 5 minutes to cover e.g. door events after trip is finished
+ // after keep alive is finished add 5 minutes to cover e.g. door events after
+ // trip is finished
runTill = Instant.now().plusMillis(KEEP_ALIVE_ADDON);
logger.trace("Websocket - keep alive stop - run till {}", runTill.toString());
}
@@ -199,8 +200,10 @@ public class MBWebsocket {
* https://community.openhab.org/t/mercedes-me/136866/12
* Release Websocket thread as early as possible to avoid execeptions
*
- * 1. Websocket thread responsible for reading stream in bytes and enqueue for AccountHandler.
- * 2. AccountHamdler thread responsible for encoding proto message. In case of update enqueue proto message
+ * 1. Websocket thread responsible for reading stream in bytes and enqueue for
+ * AccountHandler.
+ * 2. AccountHamdler thread responsible for encoding proto message. In case of
+ * update enqueue proto message
* at VehicleHandöer
* 3. VehicleHandler responsible to update channels
*/
@@ -225,6 +228,7 @@ public class MBWebsocket {
@OnWebSocketError
public void onError(Throwable t) {
+ logger.debug("Error during web socket connection - {}", t.getMessage());
accountHandler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/mercedesme.account.status.websocket-failure [\"" + t.getMessage() + "\"]");
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java
index 0954469607e..b64c5a76398 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java
+++ b/bundles/org.openhab.binding.mercedesme/src/main/java/org/openhab/binding/mercedesme/internal/utils/Utils.java
@@ -13,10 +13,8 @@
package org.openhab.binding.mercedesme.internal.utils;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
@@ -37,7 +35,6 @@ import org.json.JSONArray;
import org.json.JSONObject;
import org.openhab.binding.mercedesme.internal.Constants;
import org.openhab.binding.mercedesme.internal.MercedesMeHandlerFactory;
-import org.openhab.binding.mercedesme.internal.server.AuthService;
import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TimeZoneProvider;
@@ -78,14 +75,13 @@ public class Utils {
private static final int R = 6371; // Radius of the earth
private static int port = 8090;
- private static TimeZoneProvider timeZoneProvider = new TimeZoneProvider() {
+ public static TimeZoneProvider timeZoneProvider = new TimeZoneProvider() {
@Override
public ZoneId getTimeZone() {
return ZoneId.systemDefault();
}
};
- private static LocaleProvider localeProvider = new LocaleProvider() {
-
+ public static LocaleProvider localeProvider = new LocaleProvider() {
@Override
public Locale getLocale() {
return Locale.getDefault();
@@ -94,10 +90,13 @@ public class Utils {
public static final Gson GSON = new Gson();
public static final Map ZONE_HASHMAP = new HashMap<>();
public static final Map PROGRAM_HASHMAP = new HashMap<>();
+ public static final AccessTokenResponse INVALID_TOKEN = new AccessTokenResponse();
public static void initialize(TimeZoneProvider tzp, LocaleProvider lp) {
timeZoneProvider = tzp;
localeProvider = lp;
+ INVALID_TOKEN.setAccessToken(Constants.NOT_SET);
+ INVALID_TOKEN.setRefreshToken(Constants.NOT_SET);
}
/**
@@ -138,27 +137,6 @@ public class Utils {
return port;
}
- /**
- * Register port for an AccountHandler
- */
- public static synchronized void addPort(int portNr) {
- if (PORTS.contains(portNr) && portNr != 99999) {
- LOGGER.warn("Port {} already occupied", portNr);
- }
- PORTS.add(portNr);
- }
-
- /**
- * Unregister port for an AccountHandler
- */
- public static synchronized void removePort(int portNr) {
- PORTS.remove(Integer.valueOf(portNr));
- }
-
- public static String getCallbackAddress(String callbackIP, int callbackPort) {
- return "http://" + callbackIP + Constants.COLON + callbackPort + Constants.CALLBACK_ENDPOINT;
- }
-
/**
* Calculate REST API server address according to region
*
@@ -286,41 +264,6 @@ public class Utils {
}
}
- /**
- * Calculate authorization config URL as pre-configuration prior to authorization call
- *
- * @param region - configured region
- * @return authorization config URL as String
- */
- public static String getAuthConfigURL(String region) {
- return getRestAPIServer(region) + "/v1/config";
- }
-
- /**
- * Calculate login app id according to region
- *
- * @param region - configured region
- * @return login app id as String
- */
- public static String getLoginAppId(String region) {
- switch (region) {
- case Constants.REGION_CHINA:
- return Constants.LOGIN_APP_ID_CN;
- default:
- return Constants.LOGIN_APP_ID;
- }
- }
-
- /**
- * Calculate authorization URL for authorization call
- *
- * @param region - configured region
- * @return authorization URL as String
- */
- public static String getAuthURL(String region) {
- return getRestAPIServer(region) + "/v1/login";
- }
-
/**
* Calculate token URL for getting token
*
@@ -337,6 +280,7 @@ public class Utils {
* @param token - Base64 String from storage
* @return AccessTokenResponse decoded from String, invalid token otherwise
*/
+ @Deprecated
public static AccessTokenResponse fromString(String token) {
try {
byte[] data = Base64.getDecoder().decode(token);
@@ -347,25 +291,7 @@ public class Utils {
} catch (IOException | ClassNotFoundException e) {
LOGGER.warn("Error converting string to token {}", e.getMessage());
}
- return AuthService.INVALID_TOKEN;
- }
-
- /**
- * Encode AccessTokenResponse as Base64 String for storage
- *
- * @param token - AccessTokenResponse to convert
- */
- public static String toString(AccessTokenResponse token) {
- try {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(token);
- oos.close();
- return Base64.getEncoder().encodeToString(baos.toByteArray());
- } catch (IOException e) {
- LOGGER.warn("Error converting token to string {}", e.getMessage());
- }
- return Constants.NOT_SET;
+ return INVALID_TOKEN;
}
/**
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml
index c73b7c5b011..d61b45b6097 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml
+++ b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/config/bridge-config.xml
@@ -19,6 +19,11 @@
+
+
+ Refresh Token from MB Token Requester app
+ "takeover previous token"
+ PIN for commands
@@ -29,15 +34,5 @@
Refresh Interval in Minutes15
-
-
- IP address for openHAB callback URL
- true
-
-
-
- Port Number for openHAB callback URL
- true
-
diff --git a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties
index a18833ea11b..461ccb4a12a 100644
--- a/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties
+++ b/bundles/org.openhab.binding.mercedesme/src/main/resources/OH-INF/i18n/mercedesme.properties
@@ -19,16 +19,14 @@ thing-type.mercedesme.hybrid.description = Conventional Fuel Vehicle with suppor
thing-type.config.mercedesme.bev.batteryCapacity.label = Battery Capacity
thing-type.config.mercedesme.bev.batteryCapacity.description = Battery capacity in kWh of vehicle
thing-type.config.mercedesme.bev.vin.label = Vehicle Identification Number
-thing-type.config.mercedesme.bridge.callbackIP.label = Callback IP Address
-thing-type.config.mercedesme.bridge.callbackIP.description = IP address for openHAB callback URL
-thing-type.config.mercedesme.bridge.callbackPort.label = Callback Port Number
-thing-type.config.mercedesme.bridge.callbackPort.description = Port Number for openHAB callback URL
thing-type.config.mercedesme.bridge.email.label = MercedesMe EMail
thing-type.config.mercedesme.bridge.email.description = EMail address for MercedesMe account
thing-type.config.mercedesme.bridge.pin.label = PIN
thing-type.config.mercedesme.bridge.pin.description = PIN for commands
thing-type.config.mercedesme.bridge.refreshInterval.label = Refresh Interval
thing-type.config.mercedesme.bridge.refreshInterval.description = Refresh Interval in Minutes
+thing-type.config.mercedesme.bridge.refreshToken.label = Refresh Token
+thing-type.config.mercedesme.bridge.refreshToken.description = Refresh Token from MB Token Requester app
thing-type.config.mercedesme.bridge.region.label = Region
thing-type.config.mercedesme.bridge.region.option.EU = Europe
thing-type.config.mercedesme.bridge.region.option.NA = North America
@@ -374,13 +372,10 @@ longitudeDescription = Longitude of the location
# thing status types
-mercedesme.account.status.authorization-needed = Manual Authorization needed at {0}
+mercedesme.account.status.authorization-needed = Generate new refresh token
+mercedesme.account.status.refresh-token-missing = Refresh token missing
mercedesme.account.status.email-missing = EMail missing
mercedesme.account.status.region-missing = Region missing
mercedesme.account.status.refresh-invalid = Refresh Interval Invalid
-mercedesme.account.status.ip-missing = Callback IP missing
-mercedesme.account.status.port-missing = Callback Port missing
-mercedesme.account.status.server-restart = Disable and enable Bridge to restart Authorization Server
mercedesme.vehicle.status.bridge-missing = Bridge not set
-mercedesme.account.status.ip-autodetect-failure = Callback IP cannot be detected
mercedesme.account.status.websocket-failure = Websocket Exception: Reason: {0}
diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java
index 7506d8f5c8a..37d4639ff06 100644
--- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java
+++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/StatusTests.java
@@ -13,12 +13,18 @@
package org.openhab.binding.mercedesme;
import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
-import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mercedesme.internal.Constants;
import org.openhab.binding.mercedesme.internal.handler.AccountHandlerMock;
@@ -33,7 +39,7 @@ import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.internal.BridgeImpl;
/**
- * {@link StatusTests} sequencess for testing ThingStatus
+ * {@link StatusTests} sequences for testing ThingStatus
*
* @author Bernd Weymann - Initial contribution
*/
@@ -41,23 +47,41 @@ import org.openhab.core.thing.internal.BridgeImpl;
class StatusTests {
public static void tearDown(AccountHandlerMock ahm) {
- // ahm.setCallback(null);
- ahm.dispose();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
fail();
}
+ ahm.dispose();
+ }
+
+ public static HttpClient getHttpClient(int tokenResponseCode) {
+ Utils.initialize(Utils.timeZoneProvider, Utils.localeProvider);
+ HttpClient httpClient = mock(HttpClient.class);
+ try {
+ Request clientRequest = mock(Request.class);
+ when(httpClient.POST(anyString())).thenReturn(clientRequest);
+ when(clientRequest.header(anyString(), anyString())).thenReturn(clientRequest);
+ when(clientRequest.content(any())).thenReturn(clientRequest);
+ when(clientRequest.timeout(anyLong(), any())).thenReturn(clientRequest);
+ ContentResponse response = mock(ContentResponse.class);
+ when(response.getStatus()).thenReturn(tokenResponseCode);
+ String tokenResponse = FileReader.readFileInString("src/test/resources/json/TokenResponse.json");
+ when(response.getContentAsString()).thenReturn(tokenResponse);
+ when(clientRequest.send()).thenReturn(response);
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ fail(e.getMessage());
+ }
+ return httpClient;
}
@Test
void testInvalidConfig() {
BridgeImpl bi = new BridgeImpl(new ThingTypeUID("test", "account"), "MB");
Map config = new HashMap<>();
- config.put("callbackIP", "999.999.999.999");
- config.put("callbackPort", "99999");
+ config.put("refreshToken", Constants.JUNIT_REFRESH_TOKEN);
bi.setConfiguration(new Configuration(config));
- AccountHandlerMock ahm = new AccountHandlerMock(bi, null);
+ AccountHandlerMock ahm = new AccountHandlerMock(bi, null, getHttpClient(404));
ThingCallbackListener tcl = new ThingCallbackListener();
ahm.setCallback(tcl);
ahm.initialize();
@@ -83,6 +107,7 @@ class StatusTests {
tcl = new ThingCallbackListener();
ahm.setCallback(tcl);
ahm.initialize();
+ ahm.refreshToken();
tsi = tcl.getThingStatus();
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Auth offline");
assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Auth detail");
@@ -107,13 +132,13 @@ class StatusTests {
config.put("refreshInterval", Integer.MAX_VALUE);
config.put("region", "row");
config.put("email", "a@b.c");
- config.put("callbackIP", "999.999.999.999");
- config.put("callbackPort", "99999");
+ config.put("refreshToken", "abc");
bi.setConfiguration(new Configuration(config));
- AccountHandlerMock ahm = new AccountHandlerMock(bi, null);
+ AccountHandlerMock ahm = new AccountHandlerMock(bi, null, getHttpClient(404));
ThingCallbackListener tcl = new ThingCallbackListener();
ahm.setCallback(tcl);
ahm.initialize();
+ ahm.refreshToken();
ThingStatusInfo tsi = tcl.getThingStatus();
assertEquals(ThingStatus.OFFLINE, tsi.getStatus(), "Auth Offline");
assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, tsi.getStatusDetail(), "Auth details");
@@ -140,17 +165,10 @@ class StatusTests {
config.put("refreshInterval", Integer.MAX_VALUE);
config.put("region", "row");
config.put("email", "a@b.c");
- config.put("callbackIP", "999.999.999.999");
- config.put("callbackPort", "99999");
+ config.put("refreshToken", "abc");
bi.setConfiguration(new Configuration(config));
- AccessTokenResponse token = new AccessTokenResponse();
- token.setExpiresIn(3000);
- token.setAccessToken(Constants.JUNIT_TOKEN);
- token.setRefreshToken(Constants.JUNIT_REFRESH_TOKEN);
- token.setCreatedOn(Instant.now());
- token.setTokenType("Bearer");
- token.setScope(Constants.SCOPE);
- AccountHandlerMock ahm = new AccountHandlerMock(bi, Utils.toString(token));
+ String tokenResponse = FileReader.readFileInString("src/test/resources/json/TokenResponse.json");
+ AccountHandlerMock ahm = new AccountHandlerMock(bi, tokenResponse, getHttpClient(200));
ThingCallbackListener tcl = new ThingCallbackListener();
ahm.setCallback(tcl);
ahm.initialize();
diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java
index b80f81190aa..48f6939ef27 100644
--- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java
+++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/AccountHandlerMock.java
@@ -15,7 +15,6 @@ package org.openhab.binding.mercedesme.internal.handler;
import static org.mockito.Mockito.mock;
import java.util.Locale;
-import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -26,7 +25,6 @@ import org.openhab.binding.mercedesme.internal.Constants;
import org.openhab.binding.mercedesme.internal.config.AccountConfiguration;
import org.openhab.binding.mercedesme.internal.discovery.MercedesMeDiscoveryService;
import org.openhab.core.i18n.LocaleProvider;
-import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.storage.Storage;
import org.openhab.core.storage.StorageService;
import org.openhab.core.test.storage.VolatileStorageService;
@@ -54,18 +52,17 @@ public class AccountHandlerMock extends AccountHandler {
public AccountHandlerMock() {
super(mock(Bridge.class), mock(MercedesMeDiscoveryService.class), mock(HttpClient.class),
- mock(LocaleProvider.class), mock(StorageService.class), mock(NetworkAddressService.class));
- config = Optional.of(new AccountConfiguration());
+ mock(LocaleProvider.class), mock(StorageService.class));
+ config = new AccountConfiguration();
}
- public AccountHandlerMock(Bridge b, @Nullable String storedObject) {
- super(b, mock(MercedesMeDiscoveryService.class), mock(HttpClient.class), localeProvider, storageService,
- mock(NetworkAddressService.class));
+ public AccountHandlerMock(Bridge b, @Nullable String storedObject, HttpClient httpClient) {
+ super(b, mock(MercedesMeDiscoveryService.class), httpClient, localeProvider, storageService);
if (storedObject != null) {
Storage storage = storageService.getStorage(Constants.BINDING_ID);
storage.put("a@b.c", storedObject);
}
- config = Optional.of(new AccountConfiguration());
+ config = new AccountConfiguration();
}
@Override
@@ -94,4 +91,8 @@ public class AccountHandlerMock extends AccountHandler {
public void connect() {
super.ws.onConnect(mock(Session.class));
}
+
+ public void refreshToken() {
+ authService.get().getToken();
+ }
}
diff --git a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java
index aa44f22512f..0fb80814bc6 100644
--- a/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java
+++ b/bundles/org.openhab.binding.mercedesme/src/test/java/org/openhab/binding/mercedesme/internal/handler/VehicleHandlerTest.java
@@ -21,6 +21,7 @@ import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.openhab.binding.mercedesme.FileReader;
import org.openhab.binding.mercedesme.internal.Constants;
@@ -61,6 +62,11 @@ class VehicleHandlerTest {
private static final int EVENT_STORAGE_COUNT = HVAC_UPDATE_COUNT + POSITIONING_UPDATE_COUNT + ECOSCORE_UPDATE_COUNT
+ 76;
+ @BeforeAll
+ public static void init() {
+ Utils.initialize(Utils.timeZoneProvider, Utils.localeProvider);
+ }
+
public static Map createBEV() {
Thing thingMock = mock(Thing.class);
when(thingMock.getThingTypeUID()).thenReturn(Constants.THING_TYPE_BEV);
diff --git a/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json b/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json
index 70e4aa0ab88..ad13e808925 100644
--- a/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json
+++ b/bundles/org.openhab.binding.mercedesme/src/test/resources/json/TokenResponse.json
@@ -1,9 +1,9 @@
{
- "accessToken": "Tkn",
- "tokenType": "Bearer",
- "expiresIn": 7199,
- "refreshToken": "RfrshTkn",
+ "access_token": "junitTestToken",
+ "token_type": "Bearer",
+ "expires_in": 7199,
+ "refresh_token": "RfrshTkn",
"scope": "openid email phone profile offline_access ciam-uid",
"state": null,
- "createdOn": "2023-10-04T01:47:08.007038393Z"
+ "created_on": "2023-10-04T01:47:08.007038393Z"
}
\ No newline at end of file