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.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;
43 * The {@link VehicleDiscovery} requests data from BMW API and is identifying
44 * the Vehicles after response
46 * @author Bernd Weymann - Initial contribution
47 * @author Martin Grassl - refactoring
49 @Component(scope = ServiceScope.PROTOTYPE, service = VehicleDiscovery.class)
51 public class VehicleDiscovery extends AbstractThingHandlerDiscoveryService<MyBMWBridgeHandler> {
53 private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
55 private static final int DISCOVERY_TIMEOUT = 10;
57 private Optional<MyBMWProxy> myBMWProxy = Optional.empty();
58 private @NonNullByDefault({}) ThingUID bridgeUid;
60 public VehicleDiscovery() {
61 super(MyBMWBridgeHandler.class, MyBMWConstants.SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
65 public void initialize() {
66 thingHandler.setVehicleDiscovery(this);
67 bridgeUid = thingHandler.getThing().getUID();
72 protected void startScan() {
73 logger.trace("VehicleDiscovery.startScan");
77 public void discoverVehicles() {
78 logger.trace("VehicleDiscovery.discoverVehicles");
80 myBMWProxy = thingHandler.getMyBmwProxy();
83 Optional<List<@NonNull Vehicle>> vehicleList = myBMWProxy.map(prox -> {
85 return prox.requestVehicles();
86 } catch (NetworkException e) {
87 throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
90 vehicleList.ifPresentOrElse(vehicles -> {
91 thingHandler.vehicleDiscoverySuccess();
92 processVehicles(vehicles);
93 }, () -> thingHandler.vehicleDiscoveryError());
94 } catch (IllegalStateException ex) {
95 thingHandler.vehicleDiscoveryError();
100 * this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
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
107 private void processVehicles(List<Vehicle> vehicleList) {
108 logger.trace("VehicleDiscovery.processVehicles");
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())
116 MyBMWConstants.SUPPORTED_THING_SET.forEach(entry -> {
117 if (entry.getId().equals(vehicleType)) {
118 ThingUID uid = new ThingUID(entry, vehicle.getVehicleBase().getVin(), bridgeUid.getId());
120 Map<String, String> properties = generateProperties(vehicle);
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();
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);
137 // the vehicle found is not yet known to OH, so put it into the inbox
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));
147 String vehicleLabel = vehicleAttributes.getBrand() + " " + vehicleAttributes.getModel();
148 thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUid)
149 .withRepresentationProperty(MyBMWConstants.VIN).withLabel(vehicleLabel)
150 .withProperties(convertedProperties).build());
157 private Map<String, String> generateProperties(Vehicle vehicle) {
158 Map<String, String> properties = new HashMap<>();
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());
167 VehicleCapabilities vehicleCapabilities = vehicle.getVehicleState().getCapabilities();
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));
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);
184 remoteServicesDisabled.append(RemoteService.DOOR_LOCK.getLabel() + Constants.SEMICOLON);
186 if (vehicleCapabilities.isUnlock()) {
187 remoteServicesEnabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
189 remoteServicesDisabled.append(RemoteService.DOOR_UNLOCK.getLabel() + Constants.SEMICOLON);
191 if (vehicleCapabilities.isLights()) {
192 remoteServicesEnabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
194 remoteServicesDisabled.append(RemoteService.LIGHT_FLASH.getLabel() + Constants.SEMICOLON);
196 if (vehicleCapabilities.isHorn()) {
197 remoteServicesEnabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
199 remoteServicesDisabled.append(RemoteService.HORN_BLOW.getLabel() + Constants.SEMICOLON);
201 if (vehicleCapabilities.isVehicleFinder()) {
202 remoteServicesEnabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
204 remoteServicesDisabled.append(RemoteService.VEHICLE_FINDER.getLabel() + Constants.SEMICOLON);
206 if (vehicleCapabilities.isVehicleFinder()) {
207 remoteServicesEnabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
209 remoteServicesDisabled.append(RemoteService.CLIMATE_NOW_START.getLabel() + Constants.SEMICOLON);
211 properties.put(MyBMWConstants.REMOTE_SERVICES_ENABLED, remoteServicesEnabled.toString().trim());
212 properties.put(MyBMWConstants.REMOTE_SERVICES_DISABLED, remoteServicesDisabled.toString().trim());