]> git.basschouten.com Git - openhab-addons.git/blob
5c7ac485d5bfe4bbe7731d7f483b41420c4441bd
[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.Optional;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
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;
43
44 import com.google.gson.JsonParseException;
45
46 /**
47  * The {@link ConnectedDriveBridgeHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author Bernd Weymann - Initial contribution
51  */
52 @NonNullByDefault
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();
60
61     public ConnectedDriveBridgeHandler(Bridge bridge, HttpClientFactory hcf) {
62         super(bridge);
63         httpClientFactory = hcf;
64     }
65
66     @Override
67     public void handleCommand(ChannelUID channelUID, Command command) {
68         // no commands available
69     }
70
71     @Override
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);
78         } else {
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));
83         }
84     }
85
86     public static boolean checkConfiguration(ConnectedDriveConfiguration config) {
87         if (Constants.EMPTY.equals(config.userName) || Constants.EMPTY.equals(config.password)) {
88             return false;
89         } else if (BimmerConstants.AUTH_SERVER_MAP.containsKey(config.region)) {
90             return true;
91         } else {
92             return false;
93         }
94     }
95
96     @Override
97     public void dispose() {
98         initializerJob.ifPresent(job -> job.cancel(true));
99     }
100
101     public void requestVehicles() {
102         proxy.ifPresent(prox -> prox.requestVehicles(this));
103     }
104
105     public String getDiscoveryFingerprint() {
106         return troubleshootFingerprint.map(fingerprint -> {
107             VehiclesContainer container = null;
108             try {
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;
114                         } else {
115                             container.vehicles.forEach(entry -> {
116                                 entry.vin = ANONYMOUS;
117                                 entry.breakdownNumber = ANONYMOUS;
118                                 if (entry.dealer != null) {
119                                     Dealer d = entry.dealer;
120                                     d.city = ANONYMOUS;
121                                     d.country = ANONYMOUS;
122                                     d.name = ANONYMOUS;
123                                     d.phone = ANONYMOUS;
124                                     d.postalCode = ANONYMOUS;
125                                     d.street = ANONYMOUS;
126                                 }
127                             });
128                             return Converter.getGson().toJson(container);
129                         }
130                     }
131                 }
132             } catch (JsonParseException jpe) {
133                 logger.debug("Cannot parse fingerprint {}", jpe.getMessage());
134             }
135             // Not a VehiclesContainer or Vehicles is empty so deliver fingerprint as it is
136             return fingerprint;
137         }).orElse(Constants.INVALID);
138     }
139
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 ######");
145     }
146
147     /**
148      * There's only the Vehicles response available
149      */
150     @Override
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 -> {
156                 try {
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;
166                                     d.city = ANONYMOUS;
167                                     d.country = ANONYMOUS;
168                                     d.name = ANONYMOUS;
169                                     d.phone = ANONYMOUS;
170                                     d.postalCode = ANONYMOUS;
171                                     d.street = ANONYMOUS;
172                                 }
173                             });
174                         }
175                         return Converter.getGson().toJson(container);
176                     }
177                 } catch (JsonParseException jpe) {
178                     logger.debug("Fingerprint parse exception {}", jpe.getMessage());
179                 }
180                 // Unparseable or not a VehiclesContainer:
181                 return response;
182             });
183         } else {
184             troubleshootFingerprint = Optional.of(Constants.EMPTY_JSON);
185         }
186         if (firstResponse) {
187             logFingerPrint();
188         }
189     }
190
191     @Override
192     public void onError(NetworkError error) {
193         boolean firstResponse = troubleshootFingerprint.isEmpty();
194         troubleshootFingerprint = Optional.of(error.toJson());
195         if (firstResponse) {
196             logFingerPrint();
197         }
198         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error.reason);
199     }
200
201     @Override
202     public Collection<Class<? extends ThingHandlerService>> getServices() {
203         return Collections.singleton(VehicleDiscovery.class);
204     }
205
206     public Optional<ConnectedDriveProxy> getProxy() {
207         return proxy;
208     }
209
210     public void setDiscoveryService(VehicleDiscovery discoveryService) {
211         this.discoveryService = Optional.of(discoveryService);
212     }
213 }