2 * Copyright (c) 2010-2022 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.bmwconnecteddrive.internal.discovery;
15 import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.SUPPORTED_THING_SET;
17 import java.lang.reflect.Field;
18 import java.util.HashMap;
20 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants;
25 import org.openhab.binding.bmwconnecteddrive.internal.dto.discovery.VehiclesContainer;
26 import org.openhab.binding.bmwconnecteddrive.internal.handler.ConnectedDriveBridgeHandler;
27 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
28 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
29 import org.openhab.core.config.core.Configuration;
30 import org.openhab.core.config.discovery.AbstractDiscoveryService;
31 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
32 import org.openhab.core.config.discovery.DiscoveryService;
33 import org.openhab.core.thing.ThingUID;
34 import org.openhab.core.thing.binding.ThingHandler;
35 import org.openhab.core.thing.binding.ThingHandlerService;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * The {@link VehicleDiscovery} requests data from ConnectedDrive and is identifying the Vehicles after response
42 * @author Bernd Weymann - Initial contribution
45 public class VehicleDiscovery extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
47 private final Logger logger = LoggerFactory.getLogger(VehicleDiscovery.class);
48 private static final int DISCOVERY_TIMEOUT = 10;
49 private Optional<ConnectedDriveBridgeHandler> bridgeHandler = Optional.empty();
51 public VehicleDiscovery() {
52 super(SUPPORTED_THING_SET, DISCOVERY_TIMEOUT, false);
55 public void onResponse(VehiclesContainer container) {
56 bridgeHandler.ifPresent(bridge -> {
57 final ThingUID bridgeUID = bridge.getThing().getUID();
58 container.vehicles.forEach(vehicle -> {
59 // the DriveTrain field in the delivered json is defining the Vehicle Type
60 String vehicleType = vehicle.driveTrain.toLowerCase();
61 SUPPORTED_THING_SET.forEach(entry -> {
62 if (entry.getId().equals(vehicleType)) {
63 ThingUID uid = new ThingUID(entry, vehicle.vin, bridgeUID.getId());
64 Map<String, String> properties = new HashMap<>();
66 if (vehicle.dealer != null) {
67 properties.put("dealer", vehicle.dealer.name);
68 properties.put("dealerAddress", vehicle.dealer.street + " " + vehicle.dealer.country + " "
69 + vehicle.dealer.postalCode + " " + vehicle.dealer.city);
70 properties.put("dealerPhone", vehicle.dealer.phone);
74 properties.put("servicesActivated", getObject(vehicle, Constants.ACTIVATED));
75 String servicesSupported = getObject(vehicle, Constants.SUPPORTED);
76 String servicesNotSupported = getObject(vehicle, Constants.NOT_SUPPORTED);
77 if (vehicle.statisticsAvailable) {
78 servicesSupported += Constants.STATISTICS;
80 servicesNotSupported += Constants.STATISTICS;
82 properties.put(Constants.SERVICES_SUPPORTED, servicesSupported);
83 properties.put("servicesNotSupported", servicesNotSupported);
84 properties.put("supportBreakdownNumber", vehicle.breakdownNumber);
87 if (vehicle.supportedChargingModes != null) {
88 properties.put("vehicleChargeModes",
89 String.join(Constants.SPACE, vehicle.supportedChargingModes));
91 if (vehicle.hasAlarmSystem) {
92 properties.put("vehicleAlarmSystem", "Available");
94 properties.put("vehicleAlarmSystem", "Not Available");
96 properties.put("vehicleBrand", vehicle.brand);
97 properties.put("vehicleBodytype", vehicle.bodytype);
98 properties.put("vehicleColor", vehicle.color);
99 properties.put("vehicleConstructionYear", Short.toString(vehicle.yearOfConstruction));
100 properties.put("vehicleDriveTrain", vehicle.driveTrain);
101 properties.put("vehicleModel", vehicle.model);
102 if (vehicle.chargingControl != null) {
103 properties.put("vehicleChargeControl", Converter.toTitleCase(vehicle.model));
106 // Update Properties for already created Things
107 bridge.getThing().getThings().forEach(vehicleThing -> {
108 Configuration c = vehicleThing.getConfiguration();
109 if (c.containsKey(ConnectedDriveConstants.VIN)) {
110 String thingVIN = c.get(ConnectedDriveConstants.VIN).toString();
111 if (vehicle.vin.equals(thingVIN)) {
112 vehicleThing.setProperties(properties);
117 // Properties needed for functional THing
118 properties.put(ConnectedDriveConstants.VIN, vehicle.vin);
119 properties.put("refreshInterval",
120 Integer.toString(ConnectedDriveConstants.DEFAULT_REFRESH_INTERVAL_MINUTES));
121 properties.put("units", ConnectedDriveConstants.UNITS_AUTODETECT);
122 properties.put("imageSize", Integer.toString(ConnectedDriveConstants.DEFAULT_IMAGE_SIZE_PX));
123 properties.put("imageViewport", ConnectedDriveConstants.DEFAULT_IMAGE_VIEWPORT);
125 String vehicleLabel = vehicle.brand + " " + vehicle.model;
126 Map<String, Object> convertedProperties = new HashMap<String, Object>(properties);
127 thingDiscovered(DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
128 .withRepresentationProperty(ConnectedDriveConstants.VIN).withLabel(vehicleLabel)
129 .withProperties(convertedProperties).build());
137 * Get all field names from a DTO with a specific value
138 * Used to get e.g. all services which are "ACTIVATED"
141 * @param compare String which needs to map with the value
142 * @return String with all field names matching this value separated with Spaces
144 public String getObject(Object dto, String compare) {
145 StringBuilder buf = new StringBuilder();
146 for (Field field : dto.getClass().getDeclaredFields()) {
148 Object value = field.get(dto);
149 if (compare.equals(value)) {
150 buf.append(Converter.capitalizeFirst(field.getName()) + Constants.SPACE);
152 } catch (IllegalArgumentException | IllegalAccessException e) {
153 logger.debug("Field {} not found {}", compare, e.getMessage());
156 return buf.toString();
160 public void setThingHandler(ThingHandler handler) {
161 if (handler instanceof ConnectedDriveBridgeHandler) {
162 bridgeHandler = Optional.of((ConnectedDriveBridgeHandler) handler);
163 bridgeHandler.get().setDiscoveryService(this);
168 public @Nullable ThingHandler getThingHandler() {
169 return bridgeHandler.orElse(null);
173 protected void startScan() {
174 bridgeHandler.ifPresent(ConnectedDriveBridgeHandler::requestVehicles);
178 public void deactivate() {