]> git.basschouten.com Git - openhab-addons.git/blob
9d35e53b18fd908cd819fe0f8b5abb4ac3f48994
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bmwconnecteddrive.internal.handler;
14
15 import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.ANONYMOUS;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConfiguration;
27 import org.openhab.binding.bmwconnecteddrive.internal.discovery.VehicleDiscovery;
28 import org.openhab.binding.bmwconnecteddrive.internal.dto.NetworkError;
29 import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.Dealer;
30 import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.VehiclesContainer;
31 import org.openhab.binding.bmwconnecteddrive.internal.utils.BimmerConstants;
32 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
33 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
34 import org.openhab.core.io.net.http.HttpClientFactory;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseBridgeHandler;
41 import org.openhab.core.thing.binding.ThingHandler;
42 import org.openhab.core.thing.binding.ThingHandlerService;
43 import org.openhab.core.types.Command;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 import com.google.gson.JsonParseException;
48
49 /**
50  * The {@link ConnectedDriveBridgeHandler} is responsible for handling commands, which are
51  * sent to one of the channels.
52  *
53  * @author Bernd Weymann - Initial contribution
54  */
55 @NonNullByDefault
56 public class ConnectedDriveBridgeHandler extends BaseBridgeHandler implements StringResponseCallback {
57     private final Logger logger = LoggerFactory.getLogger(ConnectedDriveBridgeHandler.class);
58     private HttpClientFactory httpClientFactory;
59     private Optional<VehicleDiscovery> discoveryService = Optional.empty();
60     private Optional<ConnectedDriveProxy> proxy = Optional.empty();
61     private Optional<ScheduledFuture<?>> initializerJob = Optional.empty();
62     private Optional<String> troubleshootFingerprint = Optional.empty();
63
64     public ConnectedDriveBridgeHandler(Bridge bridge, HttpClientFactory hcf) {
65         super(bridge);
66         httpClientFactory = hcf;
67     }
68
69     @Override
70     public void handleCommand(ChannelUID channelUID, Command command) {
71         // no commands available
72     }
73
74     @Override
75     public void initialize() {
76         troubleshootFingerprint = Optional.empty();
77         updateStatus(ThingStatus.UNKNOWN);
78         ConnectedDriveConfiguration config = getConfigAs(ConnectedDriveConfiguration.class);
79         logger.debug("Prefer MyBMW API {}", config.preferMyBmw);
80         if (!checkConfiguration(config)) {
81             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
82         } else {
83             proxy = Optional.of(new ConnectedDriveProxy(httpClientFactory, config));
84             // give the system some time to create all predefined Vehicles
85             // check with API call if bridge is online
86             initializerJob = Optional.of(scheduler.schedule(this::requestVehicles, 2, TimeUnit.SECONDS));
87             Bridge b = super.getThing();
88             List<Thing> children = b.getThings();
89             logger.debug("Update {} things", children.size());
90             children.forEach(entry -> {
91                 ThingHandler th = entry.getHandler();
92                 if (th != null) {
93                     th.dispose();
94                     th.initialize();
95                 } else {
96                     logger.debug("Handler is null");
97                 }
98             });
99         }
100     }
101
102     public static boolean checkConfiguration(ConnectedDriveConfiguration config) {
103         if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
104             return false;
105         } else {
106             return BimmerConstants.AUTH_SERVER_MAP.containsKey(config.region);
107         }
108     }
109
110     @Override
111     public void dispose() {
112         initializerJob.ifPresent(job -> job.cancel(true));
113     }
114
115     public void requestVehicles() {
116         proxy.ifPresent(prox -> prox.requestVehicles(this));
117     }
118
119     // https://www.bmw-connecteddrive.de/api/me/vehicles/v2?all=true&brand=BM
120     public String getDiscoveryFingerprint() {
121         return troubleshootFingerprint.map(fingerprint -> {
122             VehiclesContainer container = null;
123             try {
124                 container = Converter.getGson().fromJson(fingerprint, VehiclesContainer.class);
125                 if (container != null) {
126                     if (container.vehicles != null) {
127                         if (container.vehicles.isEmpty()) {
128                             return Constants.EMPTY_JSON;
129                         } else {
130                             container.vehicles.forEach(entry -> {
131                                 entry.vin = ANONYMOUS;
132                                 entry.breakdownNumber = ANONYMOUS;
133                                 if (entry.dealer != null) {
134                                     Dealer d = entry.dealer;
135                                     d.city = ANONYMOUS;
136                                     d.country = ANONYMOUS;
137                                     d.name = ANONYMOUS;
138                                     d.phone = ANONYMOUS;
139                                     d.postalCode = ANONYMOUS;
140                                     d.street = ANONYMOUS;
141                                 }
142                             });
143                             return Converter.getGson().toJson(container);
144                         }
145                     } else {
146                         logger.debug("container.vehicles is null");
147                     }
148                 }
149             } catch (JsonParseException jpe) {
150                 logger.debug("Cannot parse fingerprint {}", jpe.getMessage());
151             }
152             // Not a VehiclesContainer or Vehicles is empty so deliver fingerprint as it is
153             return fingerprint;
154         }).orElse(Constants.INVALID);
155     }
156
157     private void logFingerPrint() {
158         logger.debug("###### Discovery Troubleshoot Fingerprint Data - BEGIN ######");
159         logger.debug("### Discovery Result ###");
160         logger.debug("{}", getDiscoveryFingerprint());
161         logger.debug("###### Discovery Troubleshoot Fingerprint Data - END ######");
162     }
163
164     /**
165      * There's only the Vehicles response available
166      */
167     @Override
168     public void onResponse(@Nullable String response) {
169         boolean firstResponse = troubleshootFingerprint.isEmpty();
170         if (response != null) {
171             updateStatus(ThingStatus.ONLINE);
172             troubleshootFingerprint = discoveryService.map(discovery -> {
173                 try {
174                     VehiclesContainer container = Converter.getGson().fromJson(response, VehiclesContainer.class);
175                     if (container != null) {
176                         if (container.vehicles != null) {
177                             discovery.onResponse(container);
178                             container.vehicles.forEach(entry -> {
179                                 entry.vin = ANONYMOUS;
180                                 entry.breakdownNumber = ANONYMOUS;
181                                 if (entry.dealer != null) {
182                                     Dealer d = entry.dealer;
183                                     d.city = ANONYMOUS;
184                                     d.country = ANONYMOUS;
185                                     d.name = ANONYMOUS;
186                                     d.phone = ANONYMOUS;
187                                     d.postalCode = ANONYMOUS;
188                                     d.street = ANONYMOUS;
189                                 }
190                             });
191                         }
192                     } else {
193                         troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
194                     }
195                 } catch (JsonParseException jpe) {
196                     logger.debug("Fingerprint parse exception {}", jpe.getMessage());
197                 }
198                 // Unparseable or not a VehiclesContainer:
199                 return response;
200             });
201         } else {
202             troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
203         }
204         if (firstResponse) {
205             logFingerPrint();
206         }
207     }
208
209     @Override
210     public void onError(NetworkError error) {
211         boolean firstResponse = troubleshootFingerprint.isEmpty();
212         troubleshootFingerprint = Optional.of(error.toJson());
213         if (firstResponse) {
214             logFingerPrint();
215         }
216         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
217     }
218
219     @Override
220     public Collection<Class<? extends ThingHandlerService>> getServices() {
221         return Collections.singleton(VehicleDiscovery.class);
222     }
223
224     public Optional<ConnectedDriveProxy> getProxy() {
225         return proxy;
226     }
227
228     public void setDiscoveryService(VehicleDiscovery discoveryService) {
229         this.discoveryService = Optional.of(discoveryService);
230     }
231 }