2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bmwconnecteddrive.internal.handler;
15 import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.ANONYMOUS;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Optional;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConfiguration;
26 import org.openhab.binding.bmwconnecteddrive.internal.discovery.VehicleDiscovery;
27 import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
28 import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.Dealer;
29 import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.VehiclesContainer;
30 import org.openhab.binding.bmwconnecteddrive.internal.utils.BimmerConstants;
31 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
32 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
33 import org.openhab.core.io.net.http.HttpClientFactory;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.binding.BaseBridgeHandler;
39 import org.openhab.core.thing.binding.ThingHandlerService;
40 import org.openhab.core.types.Command;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.google.gson.JsonParseException;
47 * The {@link ConnectedDriveBridgeHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author Bernd Weymann - Initial contribution
53 public class ConnectedDriveBridgeHandler extends BaseBridgeHandler implements StringResponseCallback {
54 private final Logger logger = LoggerFactory.getLogger(ConnectedDriveBridgeHandler.class);
55 private HttpClientFactory httpClientFactory;
56 private Optional<VehicleDiscovery> discoveryService = Optional.empty();
57 private Optional<ConnectedDriveProxy> proxy = Optional.empty();
58 private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
59 private Optional<String> troubleshootFingerprint = Optional.empty();
61 public ConnectedDriveBridgeHandler(Bridge bridge, HttpClientFactory hcf) {
63 httpClientFactory = hcf;
67 public void handleCommand(ChannelUID channelUID, Command command) {
68 // no commands available
72 public void initialize() {
73 troubleshootFingerprint = Optional.empty();
74 updateStatus(ThingStatus.UNKNOWN);
75 ConnectedDriveConfiguration config = getConfigAs(ConnectedDriveConfiguration.class);
76 if (!checkConfiguration(config)) {
77 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
79 proxy = Optional.of(new ConnectedDriveProxy(httpClientFactory, config));
80 // give the system some time to create all predefined Vehicles
81 // check with API call if bridge is online
82 initializerJob = Optional.of(scheduler.schedule(this::requestVehicles, 5, TimeUnit.SECONDS));
86 public static boolean checkConfiguration(ConnectedDriveConfiguration config) {
87 if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
89 } else if (BimmerConstants.AUTH_SERVER_MAP.containsKey(config.region)) {
97 public void dispose() {
98 initializerJob.ifPresent(job -> job.cancel(true));
101 public void requestVehicles() {
102 proxy.ifPresent(prox -> prox.requestVehicles(this));
105 public String getDiscoveryFingerprint() {
106 return troubleshootFingerprint.map(fingerprint -> {
107 VehiclesContainer container = null;
109 container = Converter.getGson().fromJson(fingerprint, VehiclesContainer.class);
110 if (container != null) {
111 if (container.vehicles != null) {
112 if (container.vehicles.isEmpty()) {
113 return Constants.EMPTY_JSON;
115 container.vehicles.forEach(entry -> {
116 entry.vin = ANONYMOUS;
117 entry.breakdownNumber = ANONYMOUS;
118 if (entry.dealer != null) {
119 Dealer d = entry.dealer;
121 d.country = ANONYMOUS;
124 d.postalCode = ANONYMOUS;
125 d.street = ANONYMOUS;
128 return Converter.getGson().toJson(container);
132 } catch (JsonParseException jpe) {
133 logger.debug("Cannot parse fingerprint {}", jpe.getMessage());
135 // Not a VehiclesContainer or Vehicles is empty so deliver fingerprint as it is
137 }).orElse(Constants.INVALID);
140 private void logFingerPrint() {
141 logger.debug("###### Discovery Troubleshoot Fingerprint Data - BEGIN ######");
142 logger.debug("### Discovery Result ###");
143 logger.debug("{}", getDiscoveryFingerprint());
144 logger.debug("###### Discovery Troubleshoot Fingerprint Data - END ######");
148 * There's only the Vehicles response available
151 public void onResponse(@Nullable String response) {
152 boolean firstResponse = troubleshootFingerprint.isEmpty();
153 if (response != null) {
154 updateStatus(ThingStatus.ONLINE);
155 troubleshootFingerprint = discoveryService.map(discovery -> {
157 VehiclesContainer container = Converter.getGson().fromJson(response, VehiclesContainer.class);
158 if (container != null) {
159 if (container.vehicles != null) {
160 discovery.onResponse(container);
161 container.vehicles.forEach(entry -> {
162 entry.vin = ANONYMOUS;
163 entry.breakdownNumber = ANONYMOUS;
164 if (entry.dealer != null) {
165 Dealer d = entry.dealer;
167 d.country = ANONYMOUS;
170 d.postalCode = ANONYMOUS;
171 d.street = ANONYMOUS;
175 return Converter.getGson().toJson(container);
177 } catch (JsonParseException jpe) {
178 logger.debug("Fingerprint parse exception {}", jpe.getMessage());
180 // Unparseable or not a VehiclesContainer:
184 troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
192 public void onError(NetworkError error) {
193 boolean firstResponse = troubleshootFingerprint.isEmpty();
194 troubleshootFingerprint = Optional.of(error.toJson());
198 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
202 public Collection<Class<? extends ThingHandlerService>> getServices() {
203 return Collections.singleton(VehicleDiscovery.class);
206 public Optional<ConnectedDriveProxy> getProxy() {
210 public void setDiscoveryService(VehicleDiscovery discoveryService) {
211 this.discoveryService = Optional.of(discoveryService);