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