2 * Copyright (c) 2010-2024 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.mybmw.internal.discovery;
15 import java.util.HashMap;
16 import java.util.List;
18 import java.util.Optional;
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;
44 * The {@link VehicleDiscovery} requests data from BMW API and is identifying
45 * the Vehicles after response
47 * @author Bernd Weymann - Initial contribution
48 * @author Martin Grassl - refactoring
51 public class VehicleDiscovery extends AbstractDiscoveryService implements ThingHandlerService {
53 private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
55 private static final int DISCOVERY_TIMEOUT = 10;
57 private Optional<MyBMWBridgeHandler> bridgeHandler = Optional.empty();
58 private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
59 private Optional<ThingUID> bridgeUid = Optional.empty();
61 public VehicleDiscovery() {
62 super(MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
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());
76 public @Nullable ThingHandler getThingHandler() {
77 return bridgeHandler.orElse(null);
81 protected void startScan() {
82 logger.trace("VehicleDiscovery.startScan");
87 public void deactivate() {
88 logger.trace("VehicleDiscovery.deactivate");
93 public void discoverVehicles() {
94 logger.trace("VehicleDiscovery.discoverVehicles");
96 myBMWProxy = bridgeHandler.get().getMyBmwProxy();
99 Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
101 return prox.requestVehicles();
102 } catch (NetworkException e) {
103 throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
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());
116 * this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
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
123 private void processVehicles(List<Vehicle> vehicleList) {
124 logger.trace("VehicleDiscovery.processVehicles");
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())
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());
136 Map<String, String> properties = generateProperties(vehicle);
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();
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);
153 // the vehicle found is not yet known to OH, so put it into the inbox
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));
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());
173 private Map<String, String> generateProperties(Vehicle vehicle) {
174 Map<String, String> properties = new HashMap<>();
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());
183 VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
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));
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);
200 remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
202 if (vehicleCapabilities.isUnlock()) {
203 remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
205 remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
207 if (vehicleCapabilities.isLights()) {
208 remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
210 remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
212 if (vehicleCapabilities.isHorn()) {
213 remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
215 remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
217 if (vehicleCapabilities.isVehicleFinder()) {
218 remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
220 remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
222 if (vehicleCapabilities.isVehicleFinder()) {
223 remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
225 remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
227 properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
228 properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());