]> git.basschouten.com Git - openhab-addons.git/blob
f2b8bdbf66c084cb21c163fc722e6d010bd053a2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.mybmw.internal.discovery;
14
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Optional;
19
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.mybmw.internal.MyBMWConstants;
24 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
25 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleAttributes;
26 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleCapabilities;
27 import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
28 import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
29 import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
30 import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
31 import org.openhab.binding.mybmw.internal.utils.Constants;
32 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
33 import org.openhab.core.config.core.Configuration;
34 import org.openhab.core.config.discovery.AbstractDiscoveryService;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingUID;
38 import org.openhab.core.thing.binding.ThingHandler;
39 import org.openhab.core.thing.binding.ThingHandlerService;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The {@link VehicleDiscovery} requests data from BMW API and is identifying
45  * the Vehicles after response
46  *
47  * @author Bernd Weymann - Initial contribution
48  * @author Martin Grassl - refactoring
49  */
50 @NonNullByDefault
51 public class VehicleDiscovery extends AbstractDiscoveryService implements ThingHandlerService {
52
53     private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
54
55     private static final int DISCOVERY_TIMEOUT = 10;
56
57     private Optional<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
58     private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
59     private Optional<ThingUID> bridgeUid = Optional.empty();
60
61     public VehicleDiscovery() {
62         super(MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
63     }
64
65     @Override
66     public void setThingHandler(ThingHandler handler) {
67         if (handler instanceof MyBMWBridgeHandler bmwBridgeHandler) {
68             logger.trace("VehicleDiscovery.setThingHandler for MybmwBridge");
69             bridgeHandler = Optional.of(bmwBridgeHandler);
70             bridgeHandler.get().setVehicleDiscovery(this);
71             bridgeUid = Optional.of(bridgeHandler.get().getThing().getUID());
72         }
73     }
74
75     @Override
76     public @Nullable ThingHandler getThingHandler() {
77         return bridgeHandler.orElse(null);
78     }
79
80     @Override
81     protected void startScan() {
82         logger.trace("VehicleDiscovery.startScan");
83         discoverVehicles();
84     }
85
86     @Override
87     public void deactivate() {
88         logger.trace("VehicleDiscovery.deactivate");
89
90         super.deactivate();
91     }
92
93     public void discoverVehicles() {
94         logger.trace("VehicleDiscovery.discoverVehicles");
95
96         myBMWProxy = bridgeHandler.get().getMyBmwProxy();
97
98         try {
99             Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
100                 try {
101                     return prox.requestVehicles();
102                 } catch (NetworkException e) {
103                     throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
104                 }
105             });
106             vehicleList.ifPresentOrElse(vehicles -> {
107                 bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoverySuccess());
108                 processVehicles(vehicles);
109             }, () -> bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError()));
110         } catch (IllegalStateException ex) {
111             bridgeHandler.ifPresent(bridge -> bridge.vehicleDiscoveryError());
112         }
113     }
114
115     /**
116      * this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
117      * 
118      * it iterates through the list of existing things and checks if the vehicles found via the API
119      * call are already known to OH. If not, it creates a new thing and puts it into the inbox
120      * 
121      * @param vehicleList
122      */
123     private void processVehicles(List<Vehicle> vehicleList) {
124         logger.trace("VehicleDiscovery.processVehicles");
125
126         vehicleList.forEach(vehicle -> {
127             // the DriveTrain field in the delivered json is defining the Vehicle Type
128             String vehicleType = VehicleStatusUtils
129                     .vehicleType(vehicle.getVehicleBase().getAttributes().getDriveTrain(),
130                             vehicle.getVehicleBase().getAttributes().getModel())
131                     .toString();
132             MyBMWConstants.SUPPORTED_THING_SET.forEach(entry -> {
133                 if (entry.getId().equals(vehicleType)) {
134                     ThingUID uid = new ThingUID(entry, vehicle.getVehicleBase().getVin(), bridgeUid.get().getId());
135
136                     Map<String, String> properties = generateProperties(vehicle);
137
138                     boolean thingFound = false;
139                     // Update Properties for already created Things
140                     List<Thing> vehicleThings = bridgeHandler.get().getThing().getThings();
141                     for (Thing vehicleThing : vehicleThings) {
142                         Configuration configuration = vehicleThing.getConfiguration();
143
144                         if (configuration.containsKey(MyBMWConstants.VIN)) {
145                             String thingVIN = configuration.get(MyBMWConstants.VIN).toString();
146                             if (vehicle.getVehicleBase().getVin().equals(thingVIN)) {
147                                 vehicleThing.setProperties(properties);
148                                 thingFound = true;
149                             }
150                         }
151                     }
152
153                     // the vehicle found is not yet known to OH, so put it into the inbox
154                     if (!thingFound) {
155                         // Properties needed for functional Thing
156                         VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
157                         Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
158                         convertedProperties.put(MyBMWConstants.VIN, vehicle.getVehicleBase().getVin());
159                         convertedProperties.put(MyBMWConstants.VEHICLE_BRAND, vehicleAttributes.getBrand());
160                         convertedProperties.put(MyBMWConstants.REFRESH_INTERVAL,
161                                 Integer.toString(MyBMWConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
162
163                         String vehicleLabel = vehicleAttributes.getBrand() + " " + vehicleAttributes.getModel();
164                         thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUid.get())
165                                 .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
166                                 .withProperties(convertedProperties).build());
167                     }
168                 }
169             });
170         });
171     }
172
173     private Map<String, String> generateProperties(Vehicle vehicle) {
174         Map<String, String> properties = new HashMap<>();
175
176         // Vehicle Properties
177         VehicleAttributes vehicleAttributes = vehicle.getVehicleBase().getAttributes();
178         properties.put(MyBMWConstants.VEHICLE_MODEL, vehicleAttributes.getModel());
179         properties.put(MyBMWConstants.VEHICLE_DRIVE_TRAIN, vehicleAttributes.getDriveTrain());
180         properties.put(MyBMWConstants.VEHICLE_CONSTRUCTION_YEAR, Integer.toString(vehicleAttributes.getYear()));
181         properties.put(MyBMWConstants.VEHICLE_BODYTYPE, vehicleAttributes.getBodyType());
182
183         VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
184
185         properties.put(MyBMWConstants.SERVICES_SUPPORTED,
186                 vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, true));
187         properties.put(MyBMWConstants.SERVICES_UNSUPPORTED,
188                 vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.SUPPORTED_SUFFIX, false));
189         properties.put(MyBMWConstants.SERVICES_ENABLED,
190                 vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, true));
191         properties.put(MyBMWConstants.SERVICES_DISABLED,
192                 vehicleCapabilities.getCapabilitiesAsString(VehicleCapabilities.ENABLED_SUFFIX, false));
193
194         // For RemoteServices we need to do it step-by-step
195         StringBuffer remoteServicesEnabled = new StringBuffer();
196         StringBuffer remoteServicesDisabled = new StringBuffer();
197         if (vehicleCapabilities.isLock()) {
198             remoteServicesEnabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
199         } else {
200             remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
201         }
202         if (vehicleCapabilities.isUnlock()) {
203             remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
204         } else {
205             remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
206         }
207         if (vehicleCapabilities.isLights()) {
208             remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
209         } else {
210             remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
211         }
212         if (vehicleCapabilities.isHorn()) {
213             remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
214         } else {
215             remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
216         }
217         if (vehicleCapabilities.isVehicleFinder()) {
218             remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
219         } else {
220             remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
221         }
222         if (vehicleCapabilities.isVehicleFinder()) {
223             remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
224         } else {
225             remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
226         }
227         properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
228         properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());
229
230         return properties;
231     }
232 }